GP-6779 Refactoring DataTypeManagers/Archives

This commit is contained in:
ghidragon
2026-09-14 14:36:34 -04:00
parent 71b052c70d
commit ff5d7a12d6
284 changed files with 13132 additions and 11825 deletions
@@ -32,8 +32,9 @@ import ghidra.framework.model.DomainFile;
import ghidra.framework.model.ProjectData;
import ghidra.framework.store.db.PackedDatabase;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.dtarchive.DataTypeArchiveFactory;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
@@ -132,7 +133,9 @@ public class IsfServer extends Thread {
private DataTypeManager openAsDataTypeArchive(String ns) throws Exception {
File gdt = new File(ns);
return FileDataTypeManager.openFileArchive(gdt, false);
FileDataTypeArchive archive =
DataTypeArchiveFactory.openReadOnly(gdt, this, TaskMonitor.DUMMY);
return archive != null ? archive.getDataTypeManager() : null;
}
private DataTypeManager openAsProgramDatabase(String ns) throws Exception {
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,14 +16,14 @@
package ghidra.trace.database;
import db.util.ErrorHandler;
import ghidra.program.database.ManagerDB;
import ghidra.program.database.ProgramDBModule;
public interface DBTraceManager extends ErrorHandler {
/**
* Invalidate this manager's caches
*
* @param all probably nothing. Check out implementations of
* {@link ManagerDB#invalidateCache(boolean)}.
* {@link ProgramDBModule#invalidateCache(boolean)}.
*/
void invalidateCache(boolean all);
}
@@ -18,7 +18,6 @@ package ghidra.trace.database.data;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import db.DBHandle;
import db.Transaction;
@@ -27,13 +26,14 @@ import ghidra.framework.model.DomainFile;
import ghidra.program.database.data.ProgramBasedDataTypeManagerDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.dtarchive.DataTypeStore;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceManager;
import ghidra.trace.database.guest.DBTraceGuestPlatform;
import ghidra.trace.database.guest.DBTracePlatformManager.DBTraceHostPlatform;
import ghidra.trace.database.guest.InternalTracePlatform;
import ghidra.trace.model.data.TraceBasedDataTypeManager;
import ghidra.util.InvalidNameException;
import ghidra.util.Lock;
import ghidra.util.UniversalID;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
@@ -42,17 +42,6 @@ import ghidra.util.task.TaskMonitor;
public class DBTraceDataTypeManager extends ProgramBasedDataTypeManagerDB
implements TraceBasedDataTypeManager, DBTraceManager {
/**
* NOTE: This "read-write" lock is actually just a compatibility wrapper around the
* {@link ghidra.util.Lock} for the entire trace database. There was a time when I dreamed of
* using an actual read-write lock (though it's not known if that'd actually achieve any
* appreciable speed up); however, inheriting the existing DataTypeManager implementation
* required its lock to be used throughout the database. Rather than convert all my code (and
* lose the distinction of where I need write vs. read locks), I just wrapped the API. So no,
* this code does not refer to the wrapper, but it does still use the lock. I keep a reference
* to it here in case I ever need it.
*/
protected final ReadWriteLock lock;
protected final DBTrace trace;
protected final InternalTracePlatform platform;
@@ -64,11 +53,10 @@ public class DBTraceDataTypeManager extends ProgramBasedDataTypeManagerDB
};
}
public DBTraceDataTypeManager(DBHandle dbh, OpenMode openMode, ReadWriteLock lock,
public DBTraceDataTypeManager(DBHandle dbh, OpenMode openMode, Lock lock,
TaskMonitor monitor, DBTrace trace, InternalTracePlatform platform)
throws CancelledException, VersionException, IOException {
super(dbh, null, openMode, computePrefix(platform), trace, trace.getLock(), monitor);
this.lock = lock; // TODO: nothing uses this local lock - not sure what its purpose is
super(dbh, null, openMode, computePrefix(platform), trace, lock, monitor);
this.trace = trace;
this.platform = platform;
@@ -79,6 +67,11 @@ public class DBTraceDataTypeManager extends ProgramBasedDataTypeManagerDB
}
}
@Override
public DataTypeStore getDataStore() {
return trace.getProgramView();
}
@Override
protected void dataSettingChanged(Address address) {
// ignored - instance settings are not current supported (no AddressMap provided)
@@ -99,16 +92,6 @@ public class DBTraceDataTypeManager extends ProgramBasedDataTypeManagerDB
return trace.getName();
}
@Override
public void setName(String name) throws InvalidNameException {
if (name == null || name.length() == 0) {
throw new InvalidNameException("Name must be at least one character long: " + name);
}
trace.setName(name);
categoryRenamed(CategoryPath.ROOT, getCategory(CategoryPath.ROOT));
}
@Override
public InternalTracePlatform getPlatform() {
return platform;
@@ -239,23 +222,12 @@ public class DBTraceDataTypeManager extends ProgramBasedDataTypeManagerDB
return trace;
}
@Override
public DomainFile getDomainFile() {
return trace.getDomainFile();
}
@Override
protected String getDomainFileID() {
DomainFile domainFile = trace.getDomainFile(); // Can be null if never saved
return domainFile == null ? null : domainFile.getFileID();
}
@Override
public String getPath() {
DomainFile domainFile = trace.getDomainFile(); // Can be null if never saved
return domainFile == null ? null : domainFile.getPathname();
}
@Override
public ArchiveType getType() {
/**
@@ -17,7 +17,6 @@ package ghidra.trace.database.guest;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import db.DBHandle;
import ghidra.framework.data.OpenMode;
@@ -33,6 +32,7 @@ import ghidra.trace.model.data.TraceBasedDataTypeManager;
import ghidra.trace.model.guest.*;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.trace.util.TraceEvents;
import ghidra.util.Lock;
import ghidra.util.LockHold;
import ghidra.util.database.DBCachedObjectStore;
import ghidra.util.database.DBCachedObjectStoreFactory;
@@ -49,7 +49,7 @@ import ghidra.util.task.TaskMonitor;
*/
public class DBTracePlatformManager implements DBTraceManager, TracePlatformManager {
protected final DBHandle dbh;
protected final ReadWriteLock lock;
protected final Lock lock;
protected final Language baseLanguage;
protected final CompilerSpec baseCompilerSpec;
protected final DBTrace trace;
@@ -157,7 +157,7 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
protected final InternalTracePlatform hostPlatform = new DBTraceHostPlatform();
public DBTracePlatformManager(DBHandle dbh, OpenMode openMode, ReadWriteLock lock,
public DBTracePlatformManager(DBHandle dbh, OpenMode openMode, Lock lock,
TaskMonitor monitor, CompilerSpec baseCompilerSpec, DBTrace trace)
throws VersionException, IOException, CancelledException {
this.dbh = dbh;
@@ -56,9 +56,8 @@ import ghidra.trace.model.bookmark.TraceBookmark;
import ghidra.trace.model.bookmark.TraceBookmarkType;
import ghidra.trace.model.data.TraceBasedDataTypeManager;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.symbol.*;
import ghidra.trace.util.TraceEvents;
@@ -599,8 +598,7 @@ public class DBTraceProgramView implements TraceProgramView {
}
protected static class OverlappingAddressRangeKeyIteratorMerger<T> extends
PairingIteratorMerger<Entry<AddressRange, T>, Entry<AddressRange, T>,
Entry<AddressRange, T>> {
PairingIteratorMerger<Entry<AddressRange, T>, Entry<AddressRange, T>, Entry<AddressRange, T>> {
protected static <T> Iterable<Pair<Entry<AddressRange, T>, Entry<AddressRange, T>>> iter(
Iterable<Entry<AddressRange, T>> left, Iterable<Entry<AddressRange, T>> right) {
@@ -1565,4 +1563,9 @@ public class DBTraceProgramView implements TraceProgramView {
}
return queues;
}
@Override
public ArchiveType getArchiveType() {
return ArchiveType.PROGRAM;
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -169,12 +169,12 @@ public class DBTraceProgramViewFunctionManager implements FunctionManager {
}
@Override
public void setProgram(ProgramDB program) {
public void setDomainObject(ProgramDB program) {
throw new UnsupportedOperationException();
}
@Override
public void programReady(OpenMode openMode, int currentRevision, TaskMonitor monitor)
public void domainObjectReady(OpenMode openMode, int currentRevision, TaskMonitor monitor)
throws IOException, CancelledException {
throw new UnsupportedOperationException();
}
@@ -20,9 +20,9 @@ import java.util.Collection;
import javax.swing.Icon;
import generic.theme.GIcon;
import ghidra.framework.model.DomainObject;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.DataTypeManagerDomainObject;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Program;
@@ -60,7 +60,7 @@ import ghidra.util.LockHold;
* specific snapshot and/or manipulate the trace as if it were a program, use
* {@link #getProgramView()}.
*/
public interface Trace extends DataTypeManagerDomainObject {
public interface Trace extends DomainObject {
Icon TRACE_ICON = new GIcon("icon.content.handler.trace");
public interface TraceProgramViewListener {
@@ -92,7 +92,6 @@ public interface Trace extends DataTypeManagerDomainObject {
* For traces, this gets the "base" or "host" {@link DataTypeManager}. For platform-specific
* managers, see {@link TracePlatform#getDataTypeManager()}.
*/
@Override
default TraceBasedDataTypeManager getDataTypeManager() {
return getBaseDataTypeManager();
}
@@ -25,7 +25,9 @@ import java.nio.file.Path;
import org.junit.*;
import db.Transaction;
import ghidra.program.database.dtarchive.DataTypeArchiveFactory;
import ghidra.program.model.data.*;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.LanguageID;
import ghidra.program.util.DefaultLanguageService;
@@ -77,22 +79,17 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
assertEquals("Testing", dtm.getName());
}
@Test
public void testSetName() throws InvalidNameException {
try (Transaction tx = trace.openTransaction("Testing")) {
dtm.setName("Another name");
}
assertEquals("Another name", trace.getName());
}
@Test
public void testAddSourceArchive() throws IOException {
StructureDataType mine = getTestDataType();
DataTypePath minePath = mine.getDataTypePath();
Path tmpDir = Files.createTempDirectory("test");
File archiveFile = tmpDir.resolve("test.gdt").toFile();
FileDataTypeManager dtm2 = FileDataTypeManager.createFileArchive(archiveFile);
try (Transaction tx = dtm2.openTransaction("Testing")) {
FileDataTypeArchive archive = DataTypeArchiveFactory.createFileArchive(archiveFile, this);
DataTypeManager dtm2 = archive.getDataTypeManager();
try (Transaction tx = archive.openTransaction("Testing")) {
dtm2.addDataType(mine, DataTypeConflictHandler.DEFAULT_HANDLER);
}
DataType got = dtm2.getDataType(minePath);
@@ -100,7 +97,7 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
try (Transaction tx = trace.openTransaction("Testing")) {
dtm.addDataType(got, DataTypeConflictHandler.DEFAULT_HANDLER);
}
dtm2.delete();
archive.delete();
// TODO: Listen for sourceArchiveAdded event
@@ -27,7 +27,8 @@ import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
import ghidra.program.model.data.StandAloneDataTypeManager.ArchiveWarning;
import ghidra.program.model.dtarchive.ArchiveWarning;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
import ghidra.util.UniversalID;
public class CompareGDTs extends GhidraScript {
@@ -35,8 +36,10 @@ public class CompareGDTs extends GhidraScript {
private File firstFile;
private File secondFile;
private File outputFile;
private FileDataTypeManager firstArchive;
private FileDataTypeManager secondArchive;
private FileDataTypeArchive archive1;
private FileDataTypeArchive archive2;
private DataTypeManager dtm1;
private DataTypeManager dtm2;
private PrintWriter printWriter;
boolean matchByName;
boolean checkPointers;
@@ -57,23 +60,24 @@ public class CompareGDTs extends GhidraScript {
}
}
firstArchive = openDataTypeArchive(firstFile, false);
if (firstArchive.getWarning() != ArchiveWarning.NONE) {
archive1 = openFileDataTypeArchive(firstFile, false);
if (archive1.getWarning() != ArchiveWarning.NONE) {
popup(
"An architecture language error occured while opening archive (see log for details)\n" +
firstFile.getPath());
return;
}
secondArchive = openDataTypeArchive(secondFile, false);
if (secondArchive.getWarning() != ArchiveWarning.NONE) {
archive2 = openFileDataTypeArchive(secondFile, false);
if (archive2.getWarning() != ArchiveWarning.NONE) {
popup(
"An architecture language error occured while opening archive (see log for details)\n" +
secondFile.getPath());
firstArchive.close();
archive1.release(this);
return;
}
dtm1 = archive1.getDataTypeManager();
dtm2 = archive2.getDataTypeManager();
matchByName = askYesNo("Match Data Types By Path Name?",
"Do you want to match data types by their path names (rather than by Universal ID)?");
checkPointers = askYesNo("Check Pointers?", "Do you want to check Pointers?");
@@ -85,8 +89,8 @@ public class CompareGDTs extends GhidraScript {
}
finally {
printWriter.close();
firstArchive.close();
secondArchive.close();
archive1.release(this);
archive2.release(this);
}
}
@@ -100,30 +104,30 @@ public class CompareGDTs extends GhidraScript {
secondFile.getAbsolutePath() + ".");
output("\nThe following data types are only in " + firstFile.getAbsolutePath() + ".");
long onlyInFirst = outputEachDataTypeOnlyInFirst(firstArchive, secondArchive);
long onlyInFirst = outputEachDataTypeOnlyInFirst(dtm1, dtm2);
output(onlyInFirst + " data types that were only in first archive.");
output("\nThe following data types are only in " + secondFile.getAbsolutePath() + ".");
long onlyInSecond = outputEachDataTypeOnlyInFirst(secondArchive, firstArchive);
long onlyInSecond = outputEachDataTypeOnlyInFirst(dtm2, dtm1);
output(onlyInSecond + " data types that were only in second archive.");
output("\nThe following are different kinds of data types.");
long differentKinds = outputWhereTypesDiffer(firstArchive, secondArchive);
long differentKinds = outputWhereTypesDiffer(dtm1, dtm2);
output(differentKinds + " data types had different types.");
output("\nThe following data types are defined differently.");
long differentDefinitions = outputWhereDefinitionsDiffer(firstArchive, secondArchive);
long differentDefinitions = outputWhereDefinitionsDiffer(dtm1, dtm2);
output(differentDefinitions + " data types had different definitions.");
output("\nThe following data types are different sizes");
long differentSizes = outputWhereSizesDiffer(firstArchive, secondArchive);
long differentSizes = outputWhereSizesDiffer(dtm1, dtm2);
output(differentSizes + " data types had different sizes.");
output("\n");
}
private long outputEachDataTypeOnlyInFirst(FileDataTypeManager dtmArchive1,
FileDataTypeManager dtmArchive2) {
private long outputEachDataTypeOnlyInFirst(DataTypeManager dtmArchive1,
DataTypeManager dtmArchive2) {
long missingCount = 0;
Iterator<DataType> allDataTypes = dtmArchive1.getAllDataTypes();
@@ -136,7 +140,7 @@ public class CompareGDTs extends GhidraScript {
return missingCount;
}
private boolean outputIfMissingDataType(DataType dataType, FileDataTypeManager dtmArchive) {
private boolean outputIfMissingDataType(DataType dataType, DataTypeManager dtmArchive) {
if (!checkPointers && dataType instanceof Pointer) {
return false;
@@ -155,7 +159,7 @@ public class CompareGDTs extends GhidraScript {
return false;
}
private DataType getMatchingDataType(DataType dataType, FileDataTypeManager dtmArchive) {
private DataType getMatchingDataType(DataType dataType, DataTypeManager dtmArchive) {
if (!matchByName) {
UniversalID universalID = dataType.getUniversalID();
@@ -172,17 +176,20 @@ public class CompareGDTs extends GhidraScript {
// find by name
List<DataType> list = new ArrayList<DataType>();
dtmArchive.findDataTypes(dataType.getName(), list );
dtmArchive.findDataTypes(dataType.getName(), list);
for (DataType dtc : list) {
if (dataType.getCategoryPath().getPath().toLowerCase().equals(dtc.getCategoryPath().getPath().toLowerCase())) {
if (dataType.getCategoryPath()
.getPath()
.toLowerCase()
.equals(dtc.getCategoryPath().getPath().toLowerCase())) {
return dtc;
}
}
return null;
}
private long outputWhereTypesDiffer(FileDataTypeManager dtmArchive1,
FileDataTypeManager dtmArchive2) {
private long outputWhereTypesDiffer(DataTypeManager dtmArchive1,
DataTypeManager dtmArchive2) {
long differCount = 0;
Iterator<DataType> allDataTypes = dtmArchive1.getAllDataTypes();
@@ -195,8 +202,8 @@ public class CompareGDTs extends GhidraScript {
return differCount;
}
private long outputWhereSizesDiffer(FileDataTypeManager dtmArchive1,
FileDataTypeManager dtmArchive2) {
private long outputWhereSizesDiffer(DataTypeManager dtmArchive1,
DataTypeManager dtmArchive2) {
long differCount = 0;
Iterator<DataType> allDataTypes = dtmArchive1.getAllDataTypes();
@@ -209,7 +216,7 @@ public class CompareGDTs extends GhidraScript {
return differCount;
}
private boolean outputIfDifferentTypes(DataType dataType, FileDataTypeManager dtmArchive) {
private boolean outputIfDifferentTypes(DataType dataType, DataTypeManager dtmArchive) {
if (!checkPointers && dataType instanceof Pointer) {
return false;
@@ -233,8 +240,8 @@ public class CompareGDTs extends GhidraScript {
return false;
}
private long outputWhereDefinitionsDiffer(FileDataTypeManager dtmArchive1,
FileDataTypeManager dtmArchive2) {
private long outputWhereDefinitionsDiffer(DataTypeManager dtmArchive1,
DataTypeManager dtmArchive2) {
long differCount = 0;
Iterator<DataType> allDataTypes = dtmArchive1.getAllDataTypes();
@@ -248,7 +255,7 @@ public class CompareGDTs extends GhidraScript {
}
private boolean outputIfDifferentDefinitions(DataType dataType,
FileDataTypeManager dtmArchive) {
DataTypeManager dtmArchive) {
if (!checkPointers && dataType instanceof Pointer) {
return false;
@@ -263,7 +270,7 @@ public class CompareGDTs extends GhidraScript {
Class<?> dtClass = dataType.getClass();
Class<?> sameNamedDtClass = matchingDataType.getClass();
if (dtClass == sameNamedDtClass) {
if (dataType instanceof Enum && (((Enum) dataType).getCount()==1)) {
if (dataType instanceof Enum && (((Enum) dataType).getCount() == 1)) {
// don't check single entry enums. Size will vary, and they are extracted defines
return checkEnum((Enum) dataType, (Enum) matchingDataType);
}
@@ -283,18 +290,18 @@ public class CompareGDTs extends GhidraScript {
return true;
}
// Check that the name is the same
if (! e1.getNames()[0].equals(e2.getNames()[0])) {
if (!e1.getNames()[0].equals(e2.getNames()[0])) {
return true;
}
// Check the value is the same
if (e1.getValues()[0] != e2.getValues()[0]) {
return true;
}
}
return false;
}
private boolean outputIfDifferentSizes(DataType dataType, FileDataTypeManager dtmArchive) {
private boolean outputIfDifferentSizes(DataType dataType, DataTypeManager dtmArchive) {
if (!checkPointers && dataType instanceof Pointer) {
return false;
@@ -309,7 +316,7 @@ public class CompareGDTs extends GhidraScript {
Class<?> dtClass = dataType.getClass();
Class<?> sameNamedDtClass = matchingDataType.getClass();
if (dtClass == sameNamedDtClass) {
if (dataType instanceof Enum && (((Enum) dataType).getCount()==1)) {
if (dataType instanceof Enum && (((Enum) dataType).getCount() == 1)) {
// don't check single entry enums. Size will vary, and they are extracted defines
return checkEnum((Enum) dataType, (Enum) matchingDataType);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -35,12 +35,14 @@ import ghidra.app.script.GhidraScript;
import ghidra.app.util.cparser.C.CParserUtils;
import ghidra.app.util.cparser.C.ParseException;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class CreateUEFIGDTArchivesScript extends GhidraScript {
private File outputDirectory;
private static String headerFilePath = "/data/HeaderFiles/git/edk2";
@Override
@@ -49,41 +51,49 @@ public class CreateUEFIGDTArchivesScript extends GhidraScript {
parseUEFIHeaders("X64", "x86:LE:64:default", "windows");
parseUEFIHeaders("Ia32", "x86:LE:32:default", "windows");
parseUEFIHeaders("AArch64", "AARCH64:LE:64:v8A", "windows");
parseUEFIHeaders("Arm", "ARM:LE:32:v8", "default");
parseUEFIHeaders("RiscV64", "RISCV:LE:64:RV64G", "gcc");
parseUEFIHeaders("LoongArch64", "Loongarch:LE:64:lp64d", "default");
}
private void parseHeaderFilesToGDT(File outputDir, String gdtName, String languageID, String compiler,
String[] filenames, String includePaths[], String[] args)
private void parseHeaderFilesToGDT(File outputDir, String gdtName, String languageID,
String compiler, String[] filenames, String includePaths[], String[] args)
throws ParseException, ghidra.app.util.cparser.CPP.ParseException, IOException {
DataTypeManager openTypes[] = null;
parseHeaderFilesToGDT(openTypes, outputDir, gdtName, languageID, compiler, filenames, includePaths, args);
parseHeaderFilesToGDT(openTypes, outputDir, gdtName, languageID, compiler, filenames,
includePaths, args);
}
private void parseHeaderFilesToGDT(DataTypeManager openTypes[], File outputDir, String gdtName, String languageID, String compiler,
String[] filenames, String[] includePaths, String[] args)
private void parseHeaderFilesToGDT(DataTypeManager openTypes[], File outputDir, String gdtName,
String languageID, String compiler, String[] filenames, String[] includePaths,
String[] args)
throws ParseException, ghidra.app.util.cparser.CPP.ParseException, IOException {
String dataTypeFile = outputDir + File.separator + gdtName + ".gdt";
File f = getArchiveFile(dataTypeFile);
FileDataTypeManager dtMgr = CParserUtils.parseHeaderFiles(openTypes, filenames,
includePaths, args, f.getAbsolutePath(), languageID, compiler, monitor);
dtMgr.save();
dtMgr.close();
String dataTypeFile = outputDir + File.separator + gdtName + ".gdt";
File f = getArchiveFile(dataTypeFile);
FileDataTypeArchive archive = CParserUtils.parseHeaderFiles(filenames,
includePaths, args, f.getAbsolutePath(), languageID, compiler, openTypes, this,
monitor);
try {
archive.save(null, TaskMonitor.DUMMY);
}
catch (CancelledException e) {
// can't happen since a DUMMY monitor was used
}
archive.release(this);
}
/**
* Turn string into a file, delete old archive if it exists
*
* @param dataTypeFile
* @param dataTypeFile the file name of the stored archive
*
* @return file
*/
@@ -99,9 +109,9 @@ public class CreateUEFIGDTArchivesScript extends GhidraScript {
}
return f;
}
public void parseUEFIHeaders(String name, String languageID, String compiler) throws Exception {
String filenames[] = {
"ProcessorBind.h",
"Uefi/UefiBaseType.h",
@@ -116,22 +126,23 @@ public class CreateUEFIGDTArchivesScript extends GhidraScript {
"Library/StandaloneMmDriverEntryPoint.h",
"Library/UefiApplicationEntryPoint.h",
"Library/UefiDriverEntryPoint.h",
headerFilePath+"/MdePkg/Include/Pi/",
headerFilePath+"/MdePkg/Include/Ppi/",
headerFilePath+"/MdePkg/Include/Protocol/",
headerFilePath+"/MdePkg/Include/IndustryStandard/",
headerFilePath + "/MdePkg/Include/Pi/",
headerFilePath + "/MdePkg/Include/Ppi/",
headerFilePath + "/MdePkg/Include/Protocol/",
headerFilePath + "/MdePkg/Include/IndustryStandard/",
};
String includePaths[] = {
headerFilePath+"/MdePkg/Include/"+name,
headerFilePath+"/MdePkg/Include",
headerFilePath + "/MdePkg/Include/" + name,
headerFilePath + "/MdePkg/Include",
};
String args[] = {
"-DSTATIC_ASSERT=static_assert"
};
parseHeaderFilesToGDT(outputDirectory, "uefi_"+name, languageID, compiler, filenames, includePaths, args);
parseHeaderFilesToGDT(outputDirectory, "uefi_" + name, languageID, compiler, filenames,
includePaths, args);
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -29,10 +29,11 @@ import ghidra.app.script.GhidraScript;
import ghidra.app.services.DataTypeManagerService;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProjectDataTypeManager;
import ghidra.program.database.data.*;
import ghidra.program.database.data.DataTypeManagerDB;
import ghidra.program.database.data.ProgramDataTypeManager;
import ghidra.program.model.data.BuiltInDataTypeManager;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.dtarchive.ProjectDataTypeArchive;
import ghidra.program.model.listing.Program;
public class FixupCompositeDataTypesScript extends GhidraScript {
@@ -50,33 +51,30 @@ public class FixupCompositeDataTypesScript extends GhidraScript {
popup("This script requires the DataTypeManagerService");
return;
}
ArrayList<DTMWrapper> dtms = new ArrayList<>();
for (DataTypeManager dtm : service.getDataTypeManagers()) {
if (dtm instanceof BuiltInDataTypeManager) {
continue;
}
if (dtm instanceof ProgramDataTypeManager) {
dtms.add(0, new DTMWrapper((ProgramDataTypeManager) dtm));
}
else if (dtm instanceof DataTypeManagerDB) {
dtms.add(new DTMWrapper((DataTypeManagerDB) dtm));
if (dtm instanceof DataTypeManagerDB dtmDB) {
dtms.add(new DTMWrapper(dtmDB));
}
}
DataTypeManagerDB dtm =
askChoice("Fixup All Composites", "Select Data Type Manager: ", dtms, dtms.get(0)).dtm;
if (dtm instanceof ProgramDataTypeManager) {
Program program = ((ProgramDataTypeManager) dtm).getProgram();
if (dtm instanceof ProgramDataTypeManager programDtm) {
Program program = programDtm.getProgram();
if (!program.hasExclusiveAccess()) {
popup("Shared program must have an exclusive checkout.");
return;
}
}
else if (dtm instanceof ProjectDataTypeManager) {
DomainFile df = ((ProjectDataTypeManager) dtm).getDomainFile();
else if (dtm.getDataStore() instanceof ProjectDataTypeArchive projectArchive) {
DomainFile df = projectArchive.getDomainFile();
if (df.isVersioned() && !df.isCheckedOutExclusive()) {
popup("Shared project archive must have an exclusive checkout.");
return;
@@ -87,7 +85,6 @@ public class FixupCompositeDataTypesScript extends GhidraScript {
popup("Selected archive must be open for update.");
return;
}
dtm.fixupComposites(monitor);
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,48 +22,49 @@
import java.io.File;
import ghidra.app.script.GhidraScript;
import ghidra.program.database.dtarchive.DataTypeArchiveFactory;
import ghidra.program.model.data.Category;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.data.StandAloneDataTypeManager.ArchiveWarning;
import ghidra.program.model.dtarchive.ArchiveWarning;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
public class SynchronizeGDTCategoryPaths extends GhidraScript {
@Override
protected void run() throws Exception {
File firstFile = askFile("Select First GDT File", "Select 1st");
try (FileDataTypeManager firstArchive =
FileDataTypeManager.openFileArchive(firstFile, false)) {
if (hasWarning(firstArchive, firstFile)) {
FileDataTypeArchive archive1 = null;
FileDataTypeArchive archive2 = null;
try {
File file1 = askFile("Select First GDT File", "Select 1st");
archive1 = DataTypeArchiveFactory.openReadOnly(file1, this, TaskMonitor.DUMMY);
if (hasWarning(archive1, file1)) {
return;
}
File file2 = askFile("Select Second GDT File", "Select 2nd");
archive2 = DataTypeArchiveFactory.openReadOnly(file2, this, TaskMonitor.DUMMY);
if (hasWarning(archive2, file2)) {
return;
}
Category firstCategory = archive1.getDataTypeManager().getRootCategory();
Category secondCategory = archive2.getDataTypeManager().getRootCategory();
File secondFile = askFile("Select Second GDT File", "Select 2nd");
try (FileDataTypeManager secondArchive =
FileDataTypeManager.openFileArchive(secondFile, true)) {
if (hasWarning(secondArchive, secondFile)) {
return;
}
int transactionID =
secondArchive.startTransaction("Synchronize Category Path Names");
try {
Category firstCategory = firstArchive.getRootCategory();
Category secondCategory = secondArchive.getRootCategory();
synchronizeCategory(firstCategory, secondCategory);
}
finally {
secondArchive.endTransaction(transactionID, true);
secondArchive.save();
secondArchive.close();
}
archive2.withTransaction("Synchronized Category Path Names",
() -> synchronizeCategory(firstCategory, secondCategory));
}
finally {
if (archive1 != null) {
archive1.release(this);
}
if (archive2 != null) {
archive2.release(this);
}
}
}
private boolean hasWarning(FileDataTypeManager archive, File file) {
private boolean hasWarning(FileDataTypeArchive archive, File file) {
ArchiveWarning warning = archive.getWarning();
if (warning == ArchiveWarning.NONE) {
return false;
@@ -94,7 +94,7 @@
<I>Data Type Manager</I> window. When a data type archive is open, it
is displayed as a node in the <I>Data Type Manager</I>
tree. Archives can be opened by the user or automatically when a program is opened which
references that archive. Data type archives can be <A href="#open_for_editing">open for
references that archive. Data type archives can be <A href="#Open_Archive_For_Editing">open for
modification</A> or as read-only. Within the <I>Data Type Manager</I> window there are
actions for opening, closing, renaming, and making archives modifiable.</P>
</BLOCKQUOTE>
@@ -243,17 +243,6 @@
the program</B>.</P>
</BLOCKQUOTE>
<H3><A name="Update_Source_Archive_Names">Update Source Archive Names</A></H3>
<BLOCKQUOTE>
<P>When new releases of Ghidra are installed, old datatype archives may be dropped. In some
cases these old archives may be replaced with a differently named archive. When this
happens, this popup action can be used on a client archive to update the old source archive
references. This popup action is only available when such an archive name change has
occurred. Any programs or other client archives which reference the old archive will have
this popup action available to update the source archive name reference.</P>
</BLOCKQUOTE>
<H3>Committing Changes To Source Archive</H3>
<BLOCKQUOTE>
@@ -349,7 +338,7 @@
files are organized into directories in a filesystem. Archives are useful for sharing data
types with other users, or making your data types available for use in other projects.
Normally, file archives are opened in a read-only mode, but can optionally be <A href=
"#open_for_editing">opened</A> for editing. Project archives are normally opened for
"#Open_Archive_For_Editing">opened</A> for editing. Project archives are normally opened for
editing, since they support sharing and version control and therefore allow more than one
user to modify them at a time. Only one user at a time can have a file archive opened for
editing.</P>
@@ -467,7 +456,7 @@
Archive</B></I> action. The archive will be removed from the tree.</P>
</BLOCKQUOTE>
<H3><A name="open_for_editing"></A>Opening a File Data Type Archive for Editing</H3>
<H3><A name="Open_Archive_For_Editing"></A>Opening a File Data Type Archive for Editing</H3>
<BLOCKQUOTE>
<P>When an archive is first opened, it is not editable. In order to make any changes to
@@ -479,13 +468,13 @@
</BLOCKQUOTE>
<DIV align="left">
<H3><A name="Unlock_Archive"></A> <A name="Lock_Archive"></A>Closing a File Data Type
<H3><A name></A> <A name="Close_Archive_For_Editing"></A>Closing a File Data Type
Archive for Editing<BR>
</H3>
</DIV>
<BLOCKQUOTE>
<P>When an archive that is <A href="#open_for_editing">open for editing</A> no longer
<P>When an archive that is <A href="#Open_Archive_For_Editing">open for editing</A> no longer
needs to be edited, then it should be put back to a read-only mode so that other users
can then modify it. Select the data type archive to close for editing, right-click on it
and select the <I><B>Close for Editing</B></I> action. If the archive has unsaved
@@ -502,7 +491,6 @@
changes will be saved and the name will be updated to not show a '*'.</P>
</BLOCKQUOTE>
<!-- Unimplemented Action
<H3><A name="Save_As"></A>Saving a File Data Type Archive to a New File<BR>
</H3>
@@ -512,7 +500,6 @@
and filename for the new archive that will be created. The tree will be updated to show
the new name for the archive (the filename). The original archive file is unaffected.</P>
</BLOCKQUOTE>
-->
<H3><A name="Undo_Archive_Change"></A>Undo Unsaved Archive Change</H3>
@@ -523,7 +510,7 @@
stack of unsaved changes. The next change which may be reverted is described by the archive's
Undo Change popup menu item. If this action is used and a change is reverted it may be re-applied by using the
<A href="#Redo_Archive_Change">Redo Change</A> action. When the data type archive is
<A href="#Save">saved</A> or <A href="#Lock_Archive">closed for editing</A> the undo/redo stack is
<A href="#Save">saved</A> or <A href="#Close_Archive_For_Editing">closed for editing</A> the undo/redo stack is
cleared.
</P>
@@ -538,7 +525,7 @@
The next reverted change which may be re-applied is described by the archive's
Redo Change popup menu item. If this action is used and a change is re-applied it may again be reverted by using the
<A href="#Undo_Archive_Change">Undo Change</A> action. When the data type archive is
<A href="#Save">saved</A> or <A href="#Lock_Archive">closed for editing</A> the undo/redo stack is
<A href="#Save">saved</A> or <A href="#Close_Archive_For_Editing">closed for editing</A> the undo/redo stack is
cleared.
</P>
@@ -550,7 +537,7 @@
<P>Deleting an archive will not only remove the archive from the tree, but will
permanently remove it from the filesystem. To delete an archive, right-click on it and
select the <I><B>Delete Archive</B></I> action. An archive file must be open for editing
before this action will appear (see <A href="#open_for_editing">Opening a Data Type
before this action will appear (see <A href="#Open_Archive_For_Editing">Opening a Data Type
Archive for Editing</A>).</P>
</BLOCKQUOTE>
@@ -615,7 +602,7 @@
<P><IMG alt="" src="help/shared/note.png">
The source archive must be editable in order to commit. File archives must be <A href=
"data_type_manager_description.htm#open_for_editing">open for editing</A> and project
"data_type_manager_description.htm#Open_Archive_For_Editing">open for editing</A> and project
archives that are under version control must be checked-out.</P>
<P><IMG alt="" src="help/shared/note.png">Any data type dependencies for a comitted
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,6 +22,7 @@ import ghidra.framework.PluggableServiceRegistry;
import ghidra.framework.data.ToolStateFactory;
import ghidra.framework.main.datatree.GhidraDataFlavorHandlerService;
import ghidra.program.database.*;
import ghidra.program.database.data.DataTypeArchiveMergeManagerFactory;
public class FoundationInitializer implements ModuleInitializer {
@Override
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,21 +16,20 @@
package ghidra.app.merge;
import ghidra.app.merge.datatypes.DataTypeMergeManager;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.ModalPluginTool;
import ghidra.program.model.data.DataTypeManagerDomainObject;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.dtarchive.ProjectDataTypeArchive;
import ghidra.program.model.listing.DataTypeArchiveChangeSet;
/**
* Top level object that manages each step of the merge/resolve conflicts
* process.
*/
public class DataTypeArchiveMergeManager extends MergeManager {
public class DataTypeArchiveMergeManager
extends MergeManager<ProjectDataTypeArchive, DataTypeArchiveChangeSet> {
public DataTypeArchiveMergeManager(DataTypeManagerDomainObject resultDtArchive,
DataTypeManagerDomainObject myDtArchive, DataTypeManagerDomainObject originalDtArchive,
DataTypeManagerDomainObject latestDtArchive, DataTypeArchiveChangeSet latestChangeSet,
public DataTypeArchiveMergeManager(ProjectDataTypeArchive resultDtArchive,
ProjectDataTypeArchive myDtArchive, ProjectDataTypeArchive originalDtArchive,
ProjectDataTypeArchive latestDtArchive, DataTypeArchiveChangeSet latestChangeSet,
DataTypeArchiveChangeSet myChangeSet) {
super(resultDtArchive, myDtArchive, originalDtArchive, latestDtArchive, latestChangeSet,
myChangeSet);
@@ -43,11 +42,8 @@ public class DataTypeArchiveMergeManager extends MergeManager {
mergeResolvers = new MergeResolver[1];
mergeResolvers[idx++] =
new DataTypeMergeManager(this, (DataTypeManagerDomainObject) resultDomainObject,
(DataTypeManagerDomainObject) myDomainObject,
(DataTypeManagerDomainObject) originalDomainObject,
(DataTypeManagerDomainObject) latestDomainObject,
(DataTypeArchiveChangeSet) latestChangeSet, (DataTypeArchiveChangeSet) myChangeSet);
new DataTypeMergeManager(this, resultDomainObject, myDomainObject, originalDomainObject,
latestDomainObject, latestChangeSet, myChangeSet);
}
/**
@@ -58,16 +54,16 @@ public class DataTypeArchiveMergeManager extends MergeManager {
* @return the indicated program version or null if a valid version isn't specified.
* @see MergeConstants
*/
public DataTypeArchive getDataTypeArchive(int version) {
public ProjectDataTypeArchive getDataTypeArchive(int version) {
switch (version) {
case MergeConstants.LATEST:
return (DataTypeArchive) latestDomainObject;
return latestDomainObject;
case MergeConstants.MY:
return (DataTypeArchive) myDomainObject;
return myDomainObject;
case MergeConstants.ORIGINAL:
return (DataTypeArchive) originalDomainObject;
return originalDomainObject;
case MergeConstants.RESULT:
return (DataTypeArchive) resultDomainObject;
return resultDomainObject;
default:
return null;
}
@@ -75,9 +71,10 @@ public class DataTypeArchiveMergeManager extends MergeManager {
@Override
protected MergeManagerPlugin createMergeManagerPlugin(ModalPluginTool mergePluginTool,
MergeManager multiUserMergeManager, DomainObject modifiableDomainObject) {
MergeManager<ProjectDataTypeArchive, DataTypeArchiveChangeSet> multiUserMergeManager,
ProjectDataTypeArchive modifiableDomainObject) {
return new DataTypeArchiveMergeManagerPlugin(mergeTool, DataTypeArchiveMergeManager.this,
(DataTypeArchive) resultDomainObject);
resultDomainObject);
}
@Override
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -21,7 +21,7 @@ import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.dtarchive.ProjectDataTypeArchive;
/**
* Plugin that provides a merge component provider for data type archives.
@@ -45,7 +45,7 @@ public class DataTypeArchiveMergeManagerPlugin extends MergeManagerPlugin {
*/
public DataTypeArchiveMergeManagerPlugin(PluginTool tool,
DataTypeArchiveMergeManager mergeManager,
DataTypeArchive dataTypeArchive) {
ProjectDataTypeArchive dataTypeArchive) {
super(tool, mergeManager, dataTypeArchive);
}
@@ -38,17 +38,20 @@ import help.HelpService;
/**
* Top level object that manages each step of the merge/resolve conflicts
* process.
* @param <T> domain object implementation class
* @param <C> domain object change set implementation class
*/
public abstract class MergeManager implements DomainObjectMergeManager {
public abstract class MergeManager<T extends DomainObject, C extends DomainObjectChangeSet>
implements DomainObjectMergeManager {
protected MergeResolver[] mergeResolvers;
protected DomainObject resultDomainObject; // where changes will be merged to
protected DomainObject myDomainObject; // source of changes to be applied
protected DomainObject originalDomainObject; // original version that was checked out
protected DomainObject latestDomainObject; // latest version of the program
protected DomainObjectChangeSet latestChangeSet;
protected DomainObjectChangeSet myChangeSet;
protected T resultDomainObject; // where changes will be merged to
protected T myDomainObject; // source of changes to be applied
protected T originalDomainObject; // original version that was checked out
protected T latestDomainObject; // latest version of the program
protected C latestChangeSet;
protected C myChangeSet;
protected MergeManagerPlugin mergePlugin;
// protected ListingMergePanelPlugin listingPlugin;
@@ -69,9 +72,9 @@ public abstract class MergeManager implements DomainObjectMergeManager {
// protected boolean isShowingListingMergePanel = false;
public MergeManager(DomainObject resultDomainObject, DomainObject myDomainObject,
DomainObject originalDomainObject, DomainObject latestDomainObject,
DomainObjectChangeSet latestChangeSet, DomainObjectChangeSet myChangeSet) {
public MergeManager(T resultDomainObject, T myDomainObject,
T originalDomainObject, T latestDomainObject,
C latestChangeSet, C myChangeSet) {
this.resultDomainObject = resultDomainObject;
this.myDomainObject = myDomainObject;
this.originalDomainObject = originalDomainObject;
@@ -95,7 +98,7 @@ public abstract class MergeManager implements DomainObjectMergeManager {
* @return the indicated program version or null if a valid version isn't specified.
* @see MergeConstants
*/
public DomainObject getDomainObject(int version) {
public T getDomainObject(int version) {
switch (version) {
case MergeConstants.LATEST:
return latestDomainObject;
@@ -208,7 +211,7 @@ public abstract class MergeManager implements DomainObjectMergeManager {
}
protected abstract MergeManagerPlugin createMergeManagerPlugin(ModalPluginTool mergePluginTool,
MergeManager multiUserMergeManager, DomainObject modifiableDomainObject);
MergeManager<T, C> multiUserMergeManager, T modifiableDomainObject);
protected abstract void initializeMerge();
@@ -33,7 +33,6 @@ import ghidra.app.nav.*;
import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.util.FieldNavigator;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.ModalPluginTool;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.util.PluginException;
@@ -50,7 +49,7 @@ import help.HelpService;
/**
* Top level object that manages each step of the merge/resolve conflicts process.
*/
public class ProgramMultiUserMergeManager extends MergeManager {
public class ProgramMultiUserMergeManager extends MergeManager<Program, ProgramChangeSet> {
private ListingMergePanelPlugin listingPlugin;
private GoToAddressLabelPlugin goToPlugin;
@@ -73,10 +72,10 @@ public class ProgramMultiUserMergeManager extends MergeManager {
@Override
protected void createMergeResolvers() {
Program resultProgram = (Program) resultDomainObject;
Program myProgram = (Program) myDomainObject;
Program originalProgram = (Program) originalDomainObject;
Program latestProgram = (Program) latestDomainObject;
Program resultProgram = resultDomainObject;
Program myProgram = myDomainObject;
Program originalProgram = originalDomainObject;
Program latestProgram = latestDomainObject;
// create the merge resolvers
int idx = 0;
mergeResolvers = new MergeResolver[8];
@@ -85,28 +84,28 @@ public class ProgramMultiUserMergeManager extends MergeManager {
mergeResolvers[idx++] =
new ProgramTreeMergeManager(this, resultProgram, myProgram, originalProgram,
latestProgram, (ProgramChangeSet) latestChangeSet, (ProgramChangeSet) myChangeSet);
latestProgram, latestChangeSet, myChangeSet);
mergeResolvers[idx++] =
new DataTypeMergeManager(this, resultProgram, myProgram, originalProgram, latestProgram,
(ProgramChangeSet) latestChangeSet, (ProgramChangeSet) myChangeSet);
latestChangeSet, myChangeSet);
mergeResolvers[idx++] =
new ProgramContextMergeManager(this, resultProgram, originalProgram, latestProgram,
myProgram, (ProgramChangeSet) latestChangeSet, (ProgramChangeSet) myChangeSet);
myProgram, latestChangeSet, myChangeSet);
mergeResolvers[idx++] =
new FunctionTagMerger(this, resultProgram, originalProgram, latestProgram, myProgram,
(ProgramChangeSet) latestChangeSet, (ProgramChangeSet) myChangeSet);
latestChangeSet, myChangeSet);
ListingMergeManager listingMergeManager =
new ListingMergeManager(this, resultProgram, originalProgram, latestProgram, myProgram,
(ProgramChangeSet) latestChangeSet, (ProgramChangeSet) myChangeSet);
latestChangeSet, myChangeSet);
mergeResolvers[idx++] = listingMergeManager;
mergeResolvers[idx++] =
new ExternalProgramMerger(this, resultProgram, originalProgram, latestProgram,
myProgram, (ProgramChangeSet) latestChangeSet, (ProgramChangeSet) myChangeSet);
myProgram, latestChangeSet, myChangeSet);
mergeResolvers[idx++] = new PropertyListMergeManager(this, resultProgram, myProgram,
originalProgram, latestProgram);
@@ -122,13 +121,13 @@ public class ProgramMultiUserMergeManager extends MergeManager {
public Program getProgram(int version) {
switch (version) {
case MergeConstants.LATEST:
return (Program) resultDomainObject;
return resultDomainObject;
case MergeConstants.MY:
return (Program) myDomainObject;
return myDomainObject;
case MergeConstants.ORIGINAL:
return (Program) originalDomainObject;
return originalDomainObject;
case MergeConstants.RESULT:
return (Program) latestDomainObject;
return latestDomainObject;
default:
return null;
}
@@ -136,15 +135,16 @@ public class ProgramMultiUserMergeManager extends MergeManager {
@Override
protected MergeManagerPlugin createMergeManagerPlugin(ModalPluginTool mergePluginTool,
MergeManager multiUserMergeManager, DomainObject modifiableDomainObject) {
MergeManager<Program, ProgramChangeSet> multiUserMergeManager,
Program modifiableDomainObject) {
return new ProgramMergeManagerPlugin(mergeTool, ProgramMultiUserMergeManager.this,
(Program) resultDomainObject);
resultDomainObject);
}
@Override
protected void initializeMerge() {
mergePanel = new ListingMergePanel(mergeTool, (Program) originalDomainObject,
(Program) resultDomainObject, (Program) myDomainObject, (Program) latestDomainObject,
mergePanel = new ListingMergePanel(mergeTool, originalDomainObject,
resultDomainObject, myDomainObject, latestDomainObject,
showListingPanels);
mergePanel.removeDomainObjectListener();
navigatable = new MergeNavigatable(mergePanel);
@@ -26,11 +26,14 @@ import org.apache.commons.lang3.Strings;
import ghidra.app.merge.*;
import ghidra.app.util.HelpTopics;
import ghidra.framework.data.DomainObjectMergeManager;
import ghidra.framework.model.DomainObject;
import ghidra.program.database.data.DataTypeManagerDB;
import ghidra.program.database.data.DataTypeUtilities;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
import ghidra.program.model.dtarchive.ProjectDataTypeArchive;
import ghidra.program.model.listing.DataTypeChangeSet;
import ghidra.program.model.listing.Program;
import ghidra.util.*;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
@@ -54,7 +57,7 @@ public class DataTypeMergeManager implements MergeResolver {
static final int OPTION_ORIGINAL = 2; // Original
private DomainObjectMergeManager mergeManager;
private DataTypeManagerDomainObject[] domainObjects = new DataTypeManagerDomainObject[4];
private DomainObject[] domainObjects = new DomainObject[4];
private DataTypeManager[] dtms = new DataTypeManager[4];
private TaskMonitor currentMonitor;
private int originalConflictOption;
@@ -93,30 +96,55 @@ public class DataTypeMergeManager implements MergeResolver {
/**
* Manager for merging the data types using the four programs.
* @param mergeManager overall merge manager for domain object
* @param resultDomainObject the program to be updated with the result of the merge.
* @param resultArchive the program to be updated with the result of the merge.
* This is the program that will actually get checked in.
* @param myDomainObject the program requesting to be checked in.
* @param originalDomainObject the program that was checked out.
* @param latestDomainObject the latest checked-in version of the program.
* @param myArchive the program requesting to be checked in.
* @param originalArchive the program that was checked out.
* @param latestArchive the latest checked-in version of the program.
* @param latestChanges the address set of changes between original and latest versioned program.
* @param myChanges the address set of changes between original and my modified program.
*/
public DataTypeMergeManager(DomainObjectMergeManager mergeManager,
DataTypeManagerDomainObject resultDomainObject,
DataTypeManagerDomainObject myDomainObject,
DataTypeManagerDomainObject originalDomainObject,
DataTypeManagerDomainObject latestDomainObject, DataTypeChangeSet latestChanges,
ProjectDataTypeArchive resultArchive,
ProjectDataTypeArchive myArchive,
ProjectDataTypeArchive originalArchive,
ProjectDataTypeArchive latestArchive,
DataTypeChangeSet latestChanges,
DataTypeChangeSet myChanges) {
this.mergeManager = mergeManager;
domainObjects[RESULT] = resultDomainObject;
domainObjects[ORIGINAL] = originalDomainObject;
domainObjects[LATEST] = latestDomainObject;
domainObjects[MY] = myDomainObject;
dtms[RESULT] = resultDomainObject.getDataTypeManager();
dtms[ORIGINAL] = originalDomainObject.getDataTypeManager();
dtms[LATEST] = latestDomainObject.getDataTypeManager();
dtms[MY] = myDomainObject.getDataTypeManager();
this.mergeManager = mergeManager;
domainObjects[RESULT] = resultArchive;
domainObjects[ORIGINAL] = originalArchive;
domainObjects[LATEST] = latestArchive;
domainObjects[MY] = myArchive;
dtms[RESULT] = resultArchive.getDataTypeManager();
dtms[ORIGINAL] = originalArchive.getDataTypeManager();
dtms[LATEST] = latestArchive.getDataTypeManager();
dtms[MY] = myArchive.getDataTypeManager();
init(latestChanges, myChanges);
}
public DataTypeMergeManager(DomainObjectMergeManager mergeManager,
Program resultProgram,
Program myProgram,
Program originalProgram,
Program latestProgram,
DataTypeChangeSet latestChanges,
DataTypeChangeSet myChanges) {
this.mergeManager = mergeManager;
domainObjects[RESULT] = resultProgram;
domainObjects[ORIGINAL] = originalProgram;
domainObjects[LATEST] = latestProgram;
domainObjects[MY] = myProgram;
dtms[RESULT] = resultProgram.getDataTypeManager();
dtms[ORIGINAL] = originalProgram.getDataTypeManager();
dtms[LATEST] = latestProgram.getDataTypeManager();
dtms[MY] = myProgram.getDataTypeManager();
init(latestChanges, myChanges);
}
private void init(DataTypeChangeSet latestChanges, DataTypeChangeSet myChanges) {
totalConflictCount = 0;
setupSourceArchiveChanges(latestChanges, myChanges);
setupDataTypeChanges(latestChanges, myChanges);
@@ -1315,7 +1343,7 @@ public class DataTypeMergeManager implements MergeResolver {
try {
resultComp =
destStruct.addBitField(resultCompDt, bfDt.getDeclaredBitSize(),
sourceComp.getFieldName(), comment);
sourceComp.getFieldName(), comment);
}
catch (InvalidDataTypeException e) {
displayError(destStruct, e);
@@ -1338,14 +1366,14 @@ public class DataTypeMergeManager implements MergeResolver {
// If I have compDt, it should now be from result DTM.
resultComp =
destStruct.add(resultCompDt, length, sourceComp.getFieldName(),
comment);
comment);
}
catch (IllegalArgumentException e) {
comment =
buildDataTypeFailureComment(sourceCompDt, e.getMessage(), comment);
resultComp =
destStruct.add(BadDataType.dataType, sourceComp.getLength(),
sourceComp.getFieldName(), comment);
sourceComp.getFieldName(), comment);
if (e.getCause() instanceof DataTypeDependencyException) {
badIdDtMsgs.put(dtId, e.getMessage());
}
@@ -1364,8 +1392,8 @@ public class DataTypeMergeManager implements MergeResolver {
try {
resultComp = destStruct
.insertBitFieldAt(sourceComp.getOffset(), sourceComp.getLength(),
bfDt.getBitOffset(), resultCompDt, bfDt.getDeclaredBitSize(),
sourceComp.getFieldName(), comment);
bfDt.getBitOffset(), resultCompDt, bfDt.getDeclaredBitSize(),
sourceComp.getFieldName(), comment);
}
catch (InvalidDataTypeException e) {
displayError(destStruct, e);
@@ -1376,7 +1404,7 @@ public class DataTypeMergeManager implements MergeResolver {
try {
resultComp =
destStruct.addBitField(primitiveBaseDt, bfDt.getDeclaredBitSize(),
sourceComp.getFieldName(), comment);
sourceComp.getFieldName(), comment);
}
catch (InvalidDataTypeException exc) {
throw new RuntimeException(exc); // unexpected
@@ -1429,7 +1457,7 @@ public class DataTypeMergeManager implements MergeResolver {
comment = buildDataTypeFailureComment(sourceCompDt, badMsg, comment);
resultComp =
destStruct.insertAtOffset(sourceComp.getOffset(), BadDataType.dataType,
sourceComp.getLength(), sourceComp.getFieldName(), comment);
sourceComp.getLength(), sourceComp.getFieldName(), comment);
}
}
}
@@ -33,9 +33,9 @@ import ghidra.framework.model.*;
import ghidra.framework.options.OptionType;
import ghidra.framework.options.Options;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.data.*;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
import ghidra.program.model.dtarchive.ProjectDataTypeArchive;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.Msg;
@@ -132,11 +132,11 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
options.registerOption(OPTION_NAME_GDT_FILEPATH, OptionType.FILE_TYPE, null, null,
OPTION_DESCRIPTION_GDT_FILEPATH,
() -> new FileChooserEditor(FileDataTypeManager.GDT_FILEFILTER));
() -> new FileChooserEditor(FileDataTypeArchive.GDT_FILEFILTER));
options.registerOption(OPTION_NAME_PROJECT_PATH, OptionType.STRING_TYPE, null, null,
OPTION_DESCRIPTION_PROJECT_PATH,
() -> new ProjectPathChooserEditor("Choose Data Type Archive",
new DefaultDomainFileFilter(DataTypeArchive.class, false)));
new DefaultDomainFileFilter(ProjectDataTypeArchive.class, false)));
}
@Override
@@ -293,7 +293,7 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
log.appendMsg("Missing project archive: %s".formatted(filename));
return List.of();
}
if (!DataTypeArchive.class.isAssignableFrom(gdtDomainFile.getDomainObjectClass())) {
if (!ProjectDataTypeArchive.class.isAssignableFrom(gdtDomainFile.getDomainObjectClass())) {
log.appendMsg("Bad project file type: %s".formatted(filename));
return List.of();
}
@@ -21,14 +21,14 @@ import java.util.Set;
import javax.swing.tree.TreePath;
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
import ghidra.app.plugin.core.datamgr.archive.DefaultDataTypeArchiveService;
import ghidra.app.plugin.core.datamgr.archive.BasicDataTypeArchiveService;
import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.*;
import ghidra.util.HelpLocation;
import ghidra.util.task.TaskMonitor;
// FIXME!! TESTING
public class DefaultDataTypeManagerService extends DefaultDataTypeArchiveService
public class DefaultDataTypeManagerService extends BasicDataTypeArchiveService
implements DataTypeManagerService {
// TODO: This implementation needs to be consolidated with the tool-based service in
@@ -0,0 +1,109 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.compositeeditor;
import java.io.IOException;
import db.DBHandle;
import ghidra.framework.data.OpenMode;
import ghidra.program.database.dtarchive.DataTypeArchiveDB;
import ghidra.program.model.data.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import utility.function.Callback;
/**
* DataTypeAchive used to edit composites (unions or structures)
*
* @param <T> The specific type of composite
*/
public class CompositeEditorDtArchiveDB<T extends Composite> extends DataTypeArchiveDB {
private int transactionId;
/**
* Special constructor for stack editor. We create a transaction that is always open for the
* life of this archive so that undo/redo is not available.
* @param originalDTM the data type manager this archive is shadowing.
* @throws IOException if an {@link IOException} occurs initializing the database.
*/
CompositeEditorDtArchiveDB(DataTypeManager originalDTM) throws IOException {
this(originalDTM, null, null, null);
// This prevents undo/redo by always have a transaction open during its full lifecycle.
transactionId = startTransaction("Composite Edit");
}
public CompositeEditorDtArchiveDB(T originalComposite, Callback changeCallback,
Callback restoredCallback) throws IOException {
this(originalComposite.getDataTypeManager(), originalComposite, changeCallback,
restoredCallback);
}
protected CompositeEditorDtArchiveDB(DataTypeManager originalDTM, T originalComposite,
Callback changeCallback,
Callback restoredCallback) throws IOException {
super(new DBHandle(), originalDTM.getName(), new Object());
CompositeViewerDataTypeManager<T> dtm = getDataTypeManager();
withTransaction("Setup for Edit", () -> {
dtm.intialize(originalDTM, originalComposite, changeCallback, restoredCallback);
});
clearUndo();
}
@SuppressWarnings("unchecked")
@Override
public CompositeViewerDataTypeManager<T> getDataTypeManager() {
return (CompositeViewerDataTypeManager<T>) super.getDataTypeManager();
}
@Override
protected CompositeViewerDataTypeManager<T> createDataTypeManager(DBHandle handle,
OpenMode openMode, TaskMonitor monitor)
throws VersionException, IOException, CancelledException {
return new CompositeViewerDataTypeManager<>(handle, openMode, this, lock, monitor);
}
@Override
public boolean isTemporary() {
return true;
}
@Override
public String getDescription() {
return "Temporary Data Type Archive For Composite Editing";
}
@Override
protected boolean isArchitectureChangeAllowed() {
return false;
}
@Override
protected void close() {
if (transactionId != 0) {
endTransaction(transactionId, changed);
}
super.close();
}
@Override
public ArchiveType getArchiveType() {
return ArchiveType.TEMPORARY;
}
}
@@ -100,9 +100,10 @@ abstract public class CompositeEditorModel<T extends Composite> extends Composit
originalDataTypePath = originalComposite.getDataTypePath();
currentName = originalComposite.getName();
// Use temporary standalone view datatype manager
viewDTM = new CompositeViewerDataTypeManager<>(viewDTM.getName(),
viewDTM.getResolvedViewComposite(), this::componentEdited, this::restoreEditor);
// Use temporary view datatype manager
T composite = viewDTM.getResolvedViewComposite();
viewDTM = CompositeViewerDataTypeManager.createUndoableInstance(composite, this::componentEdited,
this::restoreEditor);
viewComposite = viewDTM.getResolvedViewComposite();
@@ -225,10 +226,9 @@ abstract public class CompositeEditorModel<T extends Composite> extends Composit
viewDTM = null;
}
// Use temporary standalone view datatype manager
viewDTM =
new CompositeViewerDataTypeManager<>(originalComposite.getDataTypeManager().getName(),
originalComposite, this::componentEdited, this::restoreEditor);
// Use temporary stand-alone view datatype archive
viewDTM = CompositeViewerDataTypeManager.createUndoableInstance(originalComposite,
this::componentEdited, this::restoreEditor);
viewComposite = viewDTM.getResolvedViewComposite();
@@ -21,13 +21,17 @@ import java.util.TreeSet;
import javax.help.UnsupportedOperationException;
import db.DBHandle;
import db.util.ErrorHandler;
import ghidra.framework.data.OpenMode;
import ghidra.framework.model.RuntimeIOException;
import ghidra.program.database.DbObject;
import ghidra.program.database.data.ArchiveDataTypeManagerDB;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.ProgramArchitecture;
import ghidra.util.Lock;
import ghidra.util.Swing;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
import utility.function.Callback;
@@ -38,25 +42,24 @@ import utility.function.Callback;
* the editor.
* @param <T> Specific {@link Composite} type being managed
*/
public class CompositeViewerDataTypeManager<T extends Composite> extends StandAloneDataTypeManager
public class CompositeViewerDataTypeManager<T extends Composite> extends ArchiveDataTypeManagerDB
implements ErrorHandler {
/**
* The data type manager for original composite data type being edited.
* This is where the edited datatype will be written back to.
*/
private final DataTypeManager originalDTM;
private final T originalComposite; // may be null if not resolved into this DTM
private final T viewComposite; // may be null if not resolved into this DTM
private DataTypeManager originalDTM;
private T originalComposite; // may be null if not resolved into this DTM
private T viewComposite; // may be null if not resolved into this DTM
// Database-backed datatype ID map, view to/from original DTM
// This is needed to account for datatype use and ID alterations across undo/redo
private final IDMapDB dataTypeIDMap;
private IDMapDB dataTypeIDMap;
// Editor transaction use only - undo/redo not supported if restoreCallback is null
private Callback restoredCallback;
private Callback changeCallback;
private int transactionId = 0;
private boolean dataTypeChanged;
// Modification count used to signal optional clearing of undo/redo stack at the end of a
@@ -66,49 +69,51 @@ public class CompositeViewerDataTypeManager<T extends Composite> extends StandAl
// datatype IDs to be checked as orphaned.
// NOTE: Orphan removal can only be done when this DTM actively manages the viewComposite
private TreeSet<Long> orphanIds = new TreeSet<>();
private int transactionCount;
/**
* Creates a data type manager that the composite editor will use internally for managing
* dependencies without resolving the actual composite being edited. A single transaction
* will be started with this instantiation and held open until this instance is closed.
* Undo/redo and datatype pruning is not be supported.
* @param rootName the root name for this data type manager (usually the program name).
* @param originalDTM the original data type manager.
*/
public CompositeViewerDataTypeManager(String rootName, DataTypeManager originalDTM) {
this(rootName, originalDTM, null, null, null);
transactionId = startTransaction("Composite Edit");
public static <T extends Composite> CompositeViewerDataTypeManager<T> createNonUndoableInstance(
DataTypeManager originalDTM) {
try {
CompositeEditorDtArchiveDB<T> archive =
new CompositeEditorDtArchiveDB<T>(originalDTM);
return archive.getDataTypeManager();
}
catch (IOException e) {
throw new RuntimeIOException(e);
}
}
/**
* Creates a data type manager that the structure editor will use internally for managing a
* structure being edited and its dependencies.
* @param rootName the root name for this data type manager (usually the program name).
* @param originalComposite the original composite data type that is being edited.
* @param changeCallback Callback will be invoked when any change is made to the view composite.
* @param restoredCallback Callback will be invoked following any undo/redo.
*/
public CompositeViewerDataTypeManager(String rootName, T originalComposite,
Callback changeCallback, Callback restoredCallback) {
this(rootName, originalComposite.getDataTypeManager(), originalComposite, changeCallback,
restoredCallback);
public static <T extends Composite> CompositeViewerDataTypeManager<T> createUndoableInstance(
T originalComposite, Callback changeCallback,
Callback restoredCallback) {
try {
CompositeEditorDtArchiveDB<T> archive =
new CompositeEditorDtArchiveDB<T>(originalComposite, changeCallback,
restoredCallback);
return archive.getDataTypeManager();
}
catch (IOException e) {
throw new RuntimeIOException(e);
}
}
/**
* Constructor
* @param rootName the root name for this data type manager (usually the program name).
* @param originalDTM the original datatype manager
* @param originalComposite the original composite data type that is being edited. (may be null)
* @param changeCallback Callback will be invoked when any change is made to the view composite.
* @param restoredCallback Callback will be invoked following any undo/redo.
*/
private CompositeViewerDataTypeManager(String rootName, DataTypeManager originalDTM,
T originalComposite, Callback changeCallback, Callback restoredCallback) {
super(rootName, originalDTM.getDataOrganization());
this.originalDTM = originalDTM;
this.originalComposite = originalComposite;
this.changeCallback = changeCallback;
this.restoredCallback = restoredCallback;
CompositeViewerDataTypeManager(DBHandle handle,
OpenMode openMode, ErrorHandler errHandler, Lock lock, TaskMonitor monitor)
throws CancelledException, VersionException, IOException {
super(handle, openMode, errHandler, lock, monitor);
}
@Override
public CompositeEditorDtArchiveDB<?> getDataStore() {
return (CompositeEditorDtArchiveDB<?>) super.getDataStore();
}
void intialize(DataTypeManager dtm, T composite, Callback changeCb, Callback restoredCb) {
this.originalDTM = dtm;
this.originalComposite = composite;
this.changeCallback = changeCb;
this.restoredCallback = restoredCb;
int txId = startTransaction("Setup for Edit");
try {
@@ -120,6 +125,7 @@ public class CompositeViewerDataTypeManager<T extends Composite> extends StandAl
endTransaction(txId, true);
}
clearUndo();
}
@SuppressWarnings("unchecked")
@@ -150,10 +156,8 @@ public class CompositeViewerDataTypeManager<T extends Composite> extends StandAl
return dbHandle.getModCount();
}
@Override
protected synchronized void clearUndo() {
// Exposes method for test use
super.clearUndo();
synchronized void clearUndo() {
archive.clearUndo();
}
@Override
@@ -197,10 +201,7 @@ public class CompositeViewerDataTypeManager<T extends Composite> extends StandAl
@Override
public synchronized void close() {
if (transactionId != 0) {
super.endTransaction(transactionId, true);
}
super.close();
getDataStore().close();
}
/**
@@ -360,10 +361,16 @@ public class CompositeViewerDataTypeManager<T extends Composite> extends StandAl
}
}
@Override
public synchronized int startTransaction(String description) {
transactionCount++;
return super.startTransaction(description);
}
@Override
public synchronized boolean endTransaction(int transactionID, boolean commit) {
if (viewComposite != null && getTransactionCount() == 1) {
if (viewComposite != null && transactionCount == 1) {
// Perform orphan removal only at the end of the outer-most transaction
synchronized (orphanIds) {
checkOrphansForRemoval(false);
@@ -371,6 +378,7 @@ public class CompositeViewerDataTypeManager<T extends Composite> extends StandAl
}
boolean committed = super.endTransaction(transactionID, commit);
transactionCount--;
if (!isTransactionActive() && flattenModCount != -1) {
if (flattenModCount != dbHandle.getModCount()) {
@@ -382,9 +390,6 @@ public class CompositeViewerDataTypeManager<T extends Composite> extends StandAl
if (committed && dataTypeChanged && changeCallback != null) {
Swing.runLater(() -> changeCallback.call());
}
if (getTransactionCount() == 0) {
dataTypeChanged = false;
}
@@ -17,15 +17,18 @@ package ghidra.app.plugin.core.cparser;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import javax.help.UnsupportedOperationException;
import javax.swing.SwingUtilities;
import docking.widgets.dialogs.MultiLineMessageDialog;
import ghidra.app.util.cparser.C.CParserUtils.CParseResults;
import ghidra.program.database.data.ProgramDataTypeManager;
import ghidra.program.database.dtarchive.DataTypeArchiveFactory;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.dtarchive.DataTypeStore;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateFileException;
import ghidra.util.task.Task;
@@ -39,16 +42,16 @@ import ghidra.util.task.TaskMonitor;
class CParserTask extends Task {
private CParserPlugin plugin;
private String[] filenames;
private String[] includePaths;
private String options;
// Language and Compiler Spec IDs valid only for new dataFileName use
private String languageId;
private String compilerSpecId;
// Either dataTypeManager or dataFileName must be set, but not both
private final DataTypeManager dataTypeManager; // specified for an existing DataTypeManager
private final File dataFile; // specified for a new file
@@ -61,6 +64,7 @@ class CParserTask extends Task {
*/
CParserTask(CParserPlugin plugin, String dataFileName) {
super("Parsing C Files", true, false, false);
Objects.requireNonNull(dataFileName, "Target datatype archive filename required");
dataTypeManager = null;
this.plugin = plugin;
this.dataFile = new File(dataFileName);
@@ -77,6 +81,7 @@ class CParserTask extends Task {
*/
public CParserTask(CParserPlugin plugin, DataTypeManager dataTypeManager) {
super("Parsing C Files", true, false, false);
Objects.requireNonNull(dataTypeManager, "Target datatype manager required");
dataFile = null;
this.plugin = plugin;
this.dataTypeManager = dataTypeManager;
@@ -101,7 +106,7 @@ class CParserTask extends Task {
this.languageId = languageId;
return this;
}
/**
* Set the compiler spec ID to be used. This ID must be defined for the specified language.
*
@@ -121,17 +126,17 @@ class CParserTask extends Task {
this.compilerSpecId = compilerSpecId;
return this;
}
public CParserTask setIncludePaths(String includePaths[]) {
this.includePaths = includePaths.clone();
return this;
}
public CParserTask setFileNames(String names[]) {
this.filenames = names.clone();
return this;
}
public CParserTask setOptions(String options) {
this.options = options;
return this;
@@ -158,16 +163,16 @@ class CParserTask extends Task {
return msg;
}
private String getParseDestination(DataTypeManager dtMgr) {
private String getParseDestination(DataTypeStore archive) {
String parseDest = "";
if (dtMgr instanceof ProgramDataTypeManager) {
parseDest = "Program " + dtMgr.getName();
if (archive instanceof Program) {
parseDest = "Program " + archive.getName();
}
else if (dtMgr instanceof FileDataTypeManager fileDtm) {
parseDest = "Archive File: " + fileDtm.getFilename();
else if (archive instanceof FileDataTypeArchive fileArchive) {
parseDest = "Archive File: " + fileArchive.getPath();
}
else {
parseDest = dtMgr.getName();
parseDest = archive.getName();
}
return parseDest;
}
@@ -175,7 +180,7 @@ class CParserTask extends Task {
@Override
public void run(TaskMonitor monitor) {
FileDataTypeManager fileDtMgr = null;
FileDataTypeArchive dataFileArchive = null;
if (dataFile != null) {
try {
if ((languageId != null) != (compilerSpecId != null)) {
@@ -184,8 +189,8 @@ class CParserTask extends Task {
compilerSpecId);
return;
}
fileDtMgr =
FileDataTypeManager.createFileArchive(dataFile, languageId, compilerSpecId);
dataFileArchive = DataTypeArchiveFactory.createFileArchive(dataFile, languageId,
compilerSpecId, this);
}
catch (IOException e) {
Msg.showError(this, plugin.getDialog().getComponent(), "Archive Failure",
@@ -194,10 +199,12 @@ class CParserTask extends Task {
}
}
DataTypeManager dtMgr = fileDtMgr != null ? fileDtMgr : dataTypeManager;
DataTypeManager dtMgr =
dataFileArchive != null ? dataFileArchive.getDataTypeManager() : dataTypeManager;
int initialDtCount = dtMgr.getDataTypeCount(true);
String archiveDetination = getParseDestination(dtMgr.getDataStore());
try {
CParseResults results = plugin.parse(filenames, includePaths, options, dtMgr, monitor);
@@ -205,10 +212,10 @@ class CParserTask extends Task {
return; // cancelled
}
if (fileDtMgr != null && dtMgr.getDataTypeCount(true) != 0) {
if (dataFileArchive != null && dtMgr.getDataTypeCount(true) != 0) {
// If archive created - save to file
try {
fileDtMgr.save();
dataFileArchive.save(null, monitor);
}
catch (DuplicateFileException e) {
Msg.showError(this, plugin.getDialog().getComponent(),
@@ -228,7 +235,7 @@ class CParserTask extends Task {
if (!results.successful()) {
MultiLineMessageDialog.showModalMessageDialog(
plugin.getDialog().getComponent(), "C-Parse Failed",
"Failed to parse header file(s) to " + getParseDestination(dtMgr),
"Failed to parse header file(s) to " + archiveDetination,
plugin.getFormattedParseMessage(msg),
MultiLineMessageDialog.INFORMATION_MESSAGE);
}
@@ -236,7 +243,7 @@ class CParserTask extends Task {
MultiLineMessageDialog.showModalMessageDialog(
plugin.getDialog().getComponent(),
"C-Parse Completed",
"Successfully parsed header file(s) to " + getParseDestination(dtMgr),
"Successfully parsed header file(s) to " + archiveDetination,
plugin.getFormattedParseMessage(msg),
MultiLineMessageDialog.INFORMATION_MESSAGE);
}
@@ -247,7 +254,7 @@ class CParserTask extends Task {
SwingUtilities.invokeLater(() -> {
MultiLineMessageDialog.showMessageDialog(plugin.getDialog().getComponent(),
"C-Parse Failed",
"Failed to parse header file(s) to " + getParseDestination(dtMgr),
"Failed to parse header file(s) to " + archiveDetination,
plugin.getFormattedParseMessage(errMsg),
MultiLineMessageDialog.ERROR_MESSAGE);
});
@@ -257,7 +264,7 @@ class CParserTask extends Task {
SwingUtilities.invokeLater(() -> {
MultiLineMessageDialog.showMessageDialog(plugin.getDialog().getComponent(),
"C-PreProcessor Parse Failed",
"Failed to parse header file(s) to " + getParseDestination(dtMgr),
"Failed to parse header file(s) to " + archiveDetination,
plugin.getFormattedParseMessage(errMsg),
MultiLineMessageDialog.ERROR_MESSAGE);
});
@@ -270,15 +277,16 @@ class CParserTask extends Task {
SwingUtilities.invokeLater(() -> {
MultiLineMessageDialog.showMessageDialog(plugin.getDialog().getComponent(),
"Error During C-Parse",
"Failed to parse header file(s) to " + getParseDestination(dtMgr),
"Failed to parse header file(s) to " + archiveDetination,
plugin.getFormattedParseMessage(errMsg),
MultiLineMessageDialog.ERROR_MESSAGE);
});
}
finally {
if (fileDtMgr != null) {
boolean deleteFile = fileDtMgr.getDataTypeCount(true) == 0;
fileDtMgr.close();
if (dataFileArchive != null) {
boolean deleteFile =
dataFileArchive.getDataTypeManager().getDataTypeCount(true) == 0;
dataFileArchive.release(this);
if (deleteFile) {
dataFile.delete();
}
@@ -46,7 +46,7 @@ import ghidra.framework.Application;
import ghidra.framework.options.SaveState;
import ghidra.framework.preferences.Preferences;
import ghidra.framework.store.db.PackedDatabase;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID;
import ghidra.util.HelpLocation;
@@ -150,7 +150,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
void writeState(SaveState saveState) {
// Get the current state if the dialog has been displayed
if (!initialBuild) {
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
currentProfileName = item.file.getName();
userDefined = item.isUserDefined;
@@ -172,7 +172,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
if (initialBuild) {
return;
}
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
if (item.isChanged) {
processItemChanged(item);
}
@@ -255,7 +255,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
});
tableListener = e -> {
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
item.isChanged = !initialBuild;
notifyContextChanged();
};
@@ -270,7 +270,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
new ExtensionFileFilter(new String[] { "h" }, "C Header Files"));
parsePathTableListener = e -> {
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
item.isChanged = !initialBuild;
notifyContextChanged();
pathPanel.getTable().repaint();
@@ -485,7 +485,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
}
private void itemChanged() {
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
if (item == null) {
return;
}
@@ -497,12 +497,12 @@ class ParseDialog extends ReusableDialogComponentProvider {
saveAction = new DockingAction("Save Profile", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
save((ComboBoxItem) comboBox.getSelectedItem());
save(comboBox.getSelectedItem());
}
@Override
public boolean isEnabledForContext(ActionContext context) {
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
return item.isChanged && item.isUserDefined;
}
};
@@ -516,7 +516,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
saveAsAction = new DockingAction("Save Profile As", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
saveAs((ComboBoxItem) comboBox.getSelectedItem());
saveAs(comboBox.getSelectedItem());
}
@Override
@@ -577,7 +577,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
@Override
public boolean isEnabledForContext(ActionContext context) {
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
return item.isUserDefined;
}
};
@@ -590,7 +590,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
}
private void refresh() {
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
if (item.isChanged) {
processItemChanged(item);
}
@@ -610,7 +610,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
private void clear() {
pathPanel.clear();
parseOptionsField.setText("");
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
item.isChanged = true;
}
@@ -681,7 +681,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
}
tableModel.removeTableModelListener(tableListener);
parsePathTableModel.removeTableModelListener(parsePathTableListener);
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
item.isChanged = false;
StringBuffer sb = new StringBuffer();
@@ -817,7 +817,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
}
private void delete() {
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
if (item.isUserDefined) {
if (OptionDialog.showOptionDialog(getComponent(), "Delete Profile?",
"Are you sure you want to delete profile " + item.getName(), "Delete",
@@ -944,8 +944,8 @@ class ParseDialog extends ReusableDialogComponentProvider {
}
String name = file.getName();
if (!file.getName().endsWith(FileDataTypeManager.SUFFIX)) {
file = new File(file.getParentFile(), name + FileDataTypeManager.SUFFIX);
if (!file.getName().endsWith(FileDataTypeArchive.SUFFIX)) {
file = new File(file.getParentFile(), name + FileDataTypeArchive.SUFFIX);
}
if (!file.exists()) {
@@ -1086,7 +1086,7 @@ class ParseDialog extends ReusableDialogComponentProvider {
}
ComboBoxItem getCurrentItem() {
ComboBoxItem item = (ComboBoxItem) comboBox.getSelectedItem();
ComboBoxItem item = comboBox.getSelectedItem();
return item;
}
@@ -1,37 +1,35 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.archive;
import ghidra.framework.GenericRunInfo;
import ghidra.framework.preferences.Preferences;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.util.filechooser.ExtensionFileFilter;
package ghidra.app.plugin.core.datamgr;
import java.awt.Component;
import java.io.File;
import docking.widgets.filechooser.GhidraFileChooser;
import ghidra.framework.GenericRunInfo;
import ghidra.framework.preferences.Preferences;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
import ghidra.util.filechooser.ExtensionFileFilter;
public class ArchiveFileChooser extends GhidraFileChooser {
public ArchiveFileChooser(Component component) {
super(component);
setFileFilter(new ExtensionFileFilter(new String[] { FileDataTypeManager.EXTENSION },
setFileFilter(new ExtensionFileFilter(new String[] { FileDataTypeArchive.EXTENSION },
"Ghidra Data Type Files"));
setApproveButtonText("Save As");
setApproveButtonToolTipText("Save As");
@@ -52,7 +50,7 @@ public class ArchiveFileChooser extends GhidraFileChooser {
}
setCurrentDirectory(projectDirectory);
String suggestedName = suggestedFileName + FileDataTypeManager.SUFFIX;
String suggestedName = suggestedFileName + FileDataTypeArchive.SUFFIX;
setSelectedFile(new File(projectDirectory, suggestedName));
File file = getSelectedFile();
@@ -68,10 +66,10 @@ public class ArchiveFileChooser extends GhidraFileChooser {
private File fixFilenameSuffix(File file) {
String filename = file.getName();
if (filename.endsWith(FileDataTypeManager.SUFFIX)) {
if (filename.endsWith(FileDataTypeArchive.SUFFIX)) {
return file;
}
filename += FileDataTypeManager.SUFFIX;
filename += FileDataTypeArchive.SUFFIX;
return new File(file.getParentFile(), filename);
}
}
@@ -0,0 +1,73 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr;
import ghidra.app.plugin.core.datamgr.archive.InvalidArchive;
import ghidra.program.model.dtarchive.PersistentDataTypeArchive;
import ghidra.program.model.dtarchive.DataTypeStore;
import ghidra.program.model.listing.Program;
/**
* Interface for notifications when archives change in the archive manager
*/
public interface ArchiveManagerListener {
/**
* Called when a new Archive is opened.
* @param archive the new archive that was opened.
*/
public void archiveOpened(PersistentDataTypeArchive archive);
/**
* Called when an archive is closed.
* @param archive the archive that was closed.
*/
public void archiveClosed(PersistentDataTypeArchive archive);
/**
* Called when a new program is activated
* @param program the program that is activated
*/
public void programOpened(Program program);
/**
* Called when the current program is deactivated
* @param program the program that is being deactivated
*/
public void programClosed(Program program);
/**
* Called when the tool attempts to open a file or project archive that is referenced by
* a program, but can't be found. This creates a object that actions can operate on to
* either attempt to find it or use it to remove the references from a program.
* @param invalidArchive the InvalidArchive object that describes what the program was trying
* to open.
*/
public void invalidArchiveAdded(InvalidArchive invalidArchive);
/**
* Called when an invalid archive is removed from the tool.
* @param invalidArchive the invalid archive object that was removed from the tool
*/
public void invalidArchiveRemoved(InvalidArchive invalidArchive);
/**
* Notification that a change to the datatypeStore has occurred. Could be a datatype or
* category was added, changed, or deleted. Could also mean that the changes were saved to
* the backing store or the that a save as operation occurred.
* @param dataTypeStore The store whose state changed
*/
public void stateChanged(DataTypeStore dataTypeStore);
}
@@ -4,16 +4,16 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main.datatree;
package ghidra.app.plugin.core.datamgr;
import java.io.IOException;
import java.util.ArrayList;
@@ -22,15 +22,19 @@ import java.util.List;
import javax.swing.Icon;
import docking.action.MenuData;
import docking.widgets.tree.GTreeState;
import generic.theme.GIcon;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.editor.DataTypeEditorManager;
import ghidra.app.plugin.core.datamgr.tree.DataTypeArchiveGTree;
import ghidra.app.services.Recover;
import ghidra.app.services.Upgrade;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.main.SaveDataDialog;
import ghidra.framework.main.datatable.DomainFileContext;
import ghidra.framework.main.datatree.UndoActionDialog;
import ghidra.framework.main.projectdata.actions.VersionControlAction;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.dtarchive.ProjectDataTypeArchive;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
@@ -39,24 +43,21 @@ import ghidra.util.task.TaskMonitor;
/**
* Action to undo checkouts for domain files in the repository.
*/
public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionControlAction {
public class ArchiveUndoCheckoutTask extends VersionControlAction {
private static final Icon ICON =
new GIcon("icon.base.util.datatree.version.control.archive.dt.checkout.undo");
private DataTypeManagerPlugin dtmPlugin;
private ArchiveProvider archiveProvider;
/**
* Creates an action to undo checkouts for domain files in the repository.
* @param plugin the plug-in that owns this action.
* @param provider provides a list of domain files to be affected by this action.
*/
public VersionControlDataTypeArchiveUndoCheckoutAction(DataTypeManagerPlugin plugin,
ArchiveProvider provider) {
public ArchiveUndoCheckoutTask(DataTypeManagerPlugin plugin) {
super("UndoCheckOut", plugin.getName(), plugin.getTool());
this.dtmPlugin = plugin;
this.archiveProvider = provider;
setPopupMenuData(new MenuData(new String[] { "Undo Checkout" }, ICON, GROUP));
setDescription("Undo checkout");
@@ -65,19 +66,22 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
@Override
public void actionPerformed(DomainFileContext context) {
undoCheckOut();
undoCheckOut((DataTypesActionContext) context);
}
@Override
public boolean isEnabledForContext(DomainFileContext context) {
if (!(context instanceof DataTypesActionContext dataTypesContext)) {
return false;
}
if (isFileSystemBusy()) {
return false; // don't block; we should get called again later
}
List<DomainFile> domainFiles = context.getSelectedFiles();
for (DomainFile domainFile : domainFiles) {
if (domainFile.isCheckedOut()) {
return true; // At least one checked out file selected.
List<ProjectDataTypeArchive> archives = dataTypesContext.getSelectedProjectArchives();
for (ProjectDataTypeArchive archive : archives) {
if (archive.getDomainFile().isCheckedOut()) {
return true;
}
}
return false;
@@ -86,19 +90,19 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
/**
* Gets the domain files from the provider and then undoes the checkout on any that are
* checked out.
* @param context
*/
protected void undoCheckOut() {
protected void undoCheckOut(DataTypesActionContext context) {
if (!checkRepositoryConnected()) {
return;
}
closeEditorsForUndoCheckOut();
List<ProjectDataTypeArchive> archives = context.getSelectedProjectArchives();
closeEditorsForUndoCheckOut(archives);
List<Archive> archiveList = archiveProvider.getArchives();
List<DomainFileArchive> unmodifiedCheckOutsList = new ArrayList<>();
List<DomainFileArchive> modifiedCheckOutsList = new ArrayList<>();
for (Archive archive2 : archiveList) {
ProjectArchive archive = (ProjectArchive) archive2;
List<ProjectDataTypeArchive> unmodifiedCheckOutsList = new ArrayList<>();
List<ProjectDataTypeArchive> modifiedCheckOutsList = new ArrayList<>();
for (ProjectDataTypeArchive archive : archives) {
DomainFile domainFile = archive.getDomainFile();
if (domainFile.isCheckedOut()) {
if (domainFile.modifiedSinceCheckout() || domainFile.isChanged()) {
@@ -119,10 +123,9 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
}
}
private void closeEditorsForUndoCheckOut() {
private void closeEditorsForUndoCheckOut(List<ProjectDataTypeArchive> archives) {
DataTypeEditorManager editorManager = dtmPlugin.getEditorManager();
List<Archive> archiveList = archiveProvider.getArchives();
for (Archive archive : archiveList) {
for (ProjectDataTypeArchive archive : archives) {
if (!editorManager.checkEditors(archive.getDataTypeManager(), true)) {
continue;
}
@@ -140,12 +143,12 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
* @param modifiedArchivesList the list of archives that have been modified
* @throws CancelledException if cancelled
*/
protected void undoCheckOuts(List<DomainFileArchive> unmodifiedArchivesList,
List<DomainFileArchive> modifiedArchivesList) throws CancelledException {
protected void undoCheckOuts(List<ProjectDataTypeArchive> unmodifiedArchivesList,
List<ProjectDataTypeArchive> modifiedArchivesList) throws CancelledException {
boolean saveCopy = false;
DomainFile[] selectedFiles = new DomainFile[0];
boolean undoWasCancelled = false;
List<DomainFileArchive> selectedArchives = modifiedArchivesList;
List<ProjectDataTypeArchive> selectedArchives = modifiedArchivesList;
// Now confirm the modified ones and undo checkout for the ones the user indicates.
if (modifiedArchivesList.size() > 0) {
UndoActionDialog dialog = new UndoActionDialog("Confirm Undo Checkout",
@@ -153,14 +156,14 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
int actionID = dialog.showDialog(tool);
if (actionID != UndoActionDialog.CANCEL) {
saveCopy = dialog.saveCopy();
selectedFiles = dialog.getSelectedDomainFiles();
DomainFile[] selectedFiles = dialog.getSelectedDomainFiles();
selectedArchives = getMatchingArchives(modifiedArchivesList, selectedFiles);
}
else {
throw new CancelledException();
}
}
if ((unmodifiedArchivesList.size() > 0) || (selectedFiles.length > 0)) {
if ((unmodifiedArchivesList.size() > 0) || (selectedArchives.size() > 0)) {
tool.execute(new DataTypeArchiveUndoCheckOutTask(unmodifiedArchivesList,
selectedArchives, saveCopy));
}
@@ -169,12 +172,12 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
}
}
private List<DomainFileArchive> getMatchingArchives(List<DomainFileArchive> archivesList,
DomainFile[] selectedFiles) {
List<DomainFileArchive> archiveList =
new ArrayList<>(selectedFiles.length);
private List<ProjectDataTypeArchive> getMatchingArchives(
List<ProjectDataTypeArchive> archivesList, DomainFile[] selectedFiles) {
List<ProjectDataTypeArchive> archiveList = new ArrayList<>(selectedFiles.length);
for (DomainFile domainFile : selectedFiles) {
DomainFileArchive archive = getArchiveForDomainFile(archivesList, domainFile);
ProjectDataTypeArchive archive = getArchiveForDomainFile(archivesList, domainFile);
if (archive != null) {
archiveList.add(archive);
}
@@ -187,9 +190,9 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
return archiveList;
}
private DomainFileArchive getArchiveForDomainFile(List<DomainFileArchive> archivesList,
DomainFile domainFile) {
for (DomainFileArchive domainFileArchive : archivesList) {
private ProjectDataTypeArchive getArchiveForDomainFile(
List<ProjectDataTypeArchive> archivesList, DomainFile domainFile) {
for (ProjectDataTypeArchive domainFileArchive : archivesList) {
if (domainFileArchive.getDomainFile() == domainFile) {
return domainFileArchive;
}
@@ -197,10 +200,10 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
return null;
}
private List<DomainFile> getDomainFileList(List<DomainFileArchive> modifiedArchivesList) {
private List<DomainFile> getDomainFileList(List<ProjectDataTypeArchive> modifiedArchivesList) {
List<DomainFile> dfList = new ArrayList<>(modifiedArchivesList.size());
for (DomainFileArchive dfArchive : modifiedArchivesList) {
dfList.add(dfArchive.getDomainFile());
for (ProjectDataTypeArchive archive : modifiedArchivesList) {
dfList.add(archive.getDomainFile());
}
return dfList;
}
@@ -224,8 +227,8 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
* Task for undoing check out of files that are in version control.
*/
private class DataTypeArchiveUndoCheckOutTask extends Task {
private List<DomainFileArchive> unmodifiedCheckOutsList;
private List<DomainFileArchive> modifiedCheckedOutFiles;
private List<ProjectDataTypeArchive> unmodifiedCheckOutsList;
private List<ProjectDataTypeArchive> modifiedCheckedOutFiles;
private boolean saveCopy;
/**
@@ -235,8 +238,8 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
* @param saveCopy true indicates that copies of the modified files should be made
* before undo of the checkout.
*/
DataTypeArchiveUndoCheckOutTask(List<DomainFileArchive> unmodifiedCheckOutsList,
List<DomainFileArchive> modifiedCheckedOutFiles, boolean saveCopy) {
DataTypeArchiveUndoCheckOutTask(List<ProjectDataTypeArchive> unmodifiedCheckOutsList,
List<ProjectDataTypeArchive> modifiedCheckedOutFiles, boolean saveCopy) {
super("Undo Check Out", true, true, true);
this.unmodifiedCheckOutsList = unmodifiedCheckOutsList;
@@ -246,21 +249,20 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
@Override
public void run(TaskMonitor monitor) {
DataTypeArchiveGTree gTree = dtmPlugin.getProvider().getGTree();
GTreeState treeState = gTree.getTreeState();
try {
for (DomainFileArchive archive : unmodifiedCheckOutsList) {
ArchiveManager archiveManager = dtmPlugin.getArchiveManager();
for (ProjectDataTypeArchive archive : unmodifiedCheckOutsList) {
DomainFile df = archive.getDomainFile();
if (df.isCheckedOut() && (dtmPlugin != null)) {
// TODO Need to close archive here if it is open.
archive.close();
archiveManager.closeArchive(archive);
df.undoCheckout(false);
// TODO Need to open the archive here if it got closed above.
dtmPlugin.openArchive(df);
archiveManager.openProjectArchiveInTask(df, DomainFile.DEFAULT_VERSION,
Upgrade.ASK, Recover.ASK, false);
}
}
for (DomainFileArchive currentArchive : modifiedCheckedOutFiles) {
for (ProjectDataTypeArchive currentArchive : modifiedCheckedOutFiles) {
monitor.checkCancelled();
DomainFile currentDF = currentArchive.getDomainFile();
@@ -271,14 +273,10 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
monitor.setMessage("Undoing Check Out " + currentDF.getName());
// TODO Need to close archive here if it is open.
currentArchive.close();
archiveManager.closeArchive(currentArchive);
currentDF.undoCheckout(saveCopy);
// TODO Need to open the archive here if it got closed above.
dtmPlugin.openArchive(currentDF);
archiveManager.openProjectArchiveInTask(currentDF, DomainFile.DEFAULT_VERSION,
Upgrade.ASK, Recover.ASK, true);
}
}
catch (CancelledException e) {
@@ -287,6 +285,7 @@ public class VersionControlDataTypeArchiveUndoCheckoutAction extends VersionCont
catch (IOException e) {
ClientUtil.handleException(repository, e, "Undo Check Out", tool.getToolFrame());
}
gTree.restoreTreeState(treeState);
}
}
@@ -0,0 +1,87 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr;
import java.io.IOException;
import ghidra.app.util.HelpTopics;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.DataTreeDialogType;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.program.database.dtarchive.DataTypeArchiveFactory;
import ghidra.program.model.dtarchive.ProjectDataTypeArchive;
import ghidra.util.*;
import ghidra.util.exception.DuplicateNameException;
public class CreateProjectArchiveDialog extends DataTreeDialog {
private Object consumer;
private ProjectDataTypeArchive archive;
public CreateProjectArchiveDialog(Object consumer) {
super(null, "Create Project Archive", DataTreeDialogType.CREATE);
this.consumer = consumer;
setHelpLocation(new HelpLocation(HelpTopics.DATA_MANAGER, "New_Project_Data_Type_Archive"));
}
public ProjectDataTypeArchive getArchive() {
return archive;
}
@Override
protected void okCallback() {
DomainFolder folder = getDomainFolder();
String name = getNameText();
if (name.length() == 0) {
setStatusText("Please enter a name");
return;
}
if (folder == null) {
setStatusText("Please select a folder");
return;
}
DomainFile file = folder.getFile(name);
if (file != null) {
setStatusText("Choose a name that doesn't exist");
return;
}
if (createArchive()) {
close();
}
}
private boolean createArchive() {
DomainFolder domainFolder = getDomainFolder();
String archiveName = getNameText();
try {
archive =
DataTypeArchiveFactory.createProjectArchive(domainFolder, archiveName, consumer);
return true;
}
catch (DuplicateNameException e) {
setStatusText("Duplicate Name: " + e.getMessage());
}
catch (InvalidNameException e) {
setStatusText("Invalid Name: " + e.getMessage());
}
catch (IOException e) {
setStatusText("Unexpected IOException!");
Msg.showError(null, getComponent(), "Unexpected Exception", e.getMessage(), e);
}
return false;
}
}
@@ -0,0 +1,105 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr;
import java.io.IOException;
import java.rmi.ConnectException;
import java.util.List;
import docking.widgets.OptionDialog;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.NotConnectedException;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.TransactionInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.dtarchive.PersistentDataTypeArchive;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
public class DataTypeArchiveSaveTask extends Task {
private static final String CONTENT_NAME = "Data Type Archive";
private PersistentDataTypeArchive archive;
private PluginTool tool;
DataTypeArchiveSaveTask(PersistentDataTypeArchive archive, PluginTool tool) {
super("Save " + archive.getName(), true, true, true);
this.archive = archive;
this.tool = tool;
}
@Override
public void run(TaskMonitor monitor) {
monitor.setMessage("Saving " + archive.getName() + "...");
if (acquireSaveLock(archive)) {
try {
archive.save(null, monitor);
}
catch (CancelledException e) {
// O.K., expected
}
catch (NotConnectedException e) {
ClientUtil.promptForReconnect(tool.getProject().getRepository(),
tool.getToolFrame());
}
catch (ConnectException e) {
ClientUtil.promptForReconnect(tool.getProject().getRepository(),
tool.getToolFrame());
}
catch (IOException e) {
ClientUtil.handleException(tool.getProject().getRepository(), e, "Save File",
tool.getToolFrame());
}
finally {
archive.unlock();
}
}
}
private boolean acquireSaveLock(DomainObject domainObject) {
if (!domainObject.lock(null)) {
String title = "Save " + CONTENT_NAME + " (Busy)";
StringBuilder buf = new StringBuilder();
buf.append("The " + CONTENT_NAME + " is currently being modified by \n");
buf.append("the following actions:\n ");
TransactionInfo t = domainObject.getCurrentTransactionInfo();
List<String> list = t.getOpenSubTransactions();
for (String element : list) {
buf.append("\n ");
buf.append(element);
}
buf.append("\n \n");
buf.append(
"WARNING! The above task(s) should be cancelled before attempting a Save.\n");
buf.append("Only proceed if unable to cancel them.\n \n");
buf.append(
"If you continue, all changes made by these tasks, as well as any other overlapping task,\n");
buf.append(
"will be LOST and subsequent transaction errors may occur while these tasks remain active.\n \n");
int result = OptionDialog.showOptionDialog(tool.getToolFrame(), title, buf.toString(),
"Save Archive!", OptionDialog.WARNING_MESSAGE);
if (result == OptionDialog.OPTION_ONE) {
domainObject.forceLock(true, "Save Archive");
return true;
}
return false;
}
return true;
}
}
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.archive;
package ghidra.app.plugin.core.datamgr;
import java.util.*;
@@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,18 +15,19 @@
*/
package ghidra.app.plugin.core.datamgr;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import java.awt.event.KeyEvent;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import ghidra.program.model.data.*;
import ghidra.program.model.dtarchive.PersistentDataTypeArchive;
import ghidra.program.model.listing.Program;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
/**
* Manages the attributes for data types; used by the manage data types
* dialog to populate the data types tree structure.
@@ -110,8 +110,8 @@ public class DataTypePropertyManager {
programDataTypesManager = null;
}
void domainObjectRestored(DataTypeManagerDomainObject domainObject) {
DataTypeManager dataTypeManager = domainObject.getDataTypeManager();
void domainObjectRestored(PersistentDataTypeArchive archive) {
DataTypeManager dataTypeManager = archive.getDataTypeManager();
if (dataTypeManager != programDataTypesManager) {
return; // Ignore since not our program data type manager.
}
@@ -25,7 +25,6 @@ import javax.swing.SwingUtilities;
import org.apache.commons.lang3.Strings;
import docking.widgets.label.GDHtmlLabel;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.util.ToolTipUtils;
import ghidra.app.util.html.HTMLDataTypeRepresentation;
import ghidra.app.util.html.MissingArchiveDataTypeHTMLRepresentation;
@@ -48,17 +47,17 @@ public class DataTypeSynchronizer {
/**
* Creates a DataTypeSynchronizer to be used for synchronizing data types between a program
* and an archive.
* @param dataTypeManagerHandler the handler that manages all the open data type managers
* @param archiveManager the handler that manages all the open data type managers
* whether built-in, program, project data type archive or file data type archive.
* @param dataTypeManager the program data type manager.
* @param source the data type source archive information indicating the associated archive for
* synchronizing.
*/
public DataTypeSynchronizer(DataTypeManagerHandler dataTypeManagerHandler,
DataTypeManager dataTypeManager, SourceArchive source) {
public DataTypeSynchronizer(ArchiveManager archiveManager, DataTypeManager dataTypeManager,
SourceArchive source) {
this.dataTypeManager = dataTypeManager;
this.sourceArchive = source;
this.sourceDTM = dataTypeManagerHandler.getDataTypeManager(source);
this.sourceDTM = archiveManager.getDataTypeManager(source);
}
public List<DataTypeSyncInfo> findOutOfSynchDataTypes() {
@@ -150,13 +149,13 @@ public class DataTypeSynchronizer {
/**
* Commits a single program data type's changes to the associated source data type in the
* archive.
* @param dtmHandler the handler that manages data types
* @param archiveManager the archive manager
* @param dt the program data type
* @return true if the commit succeeds
*/
public static boolean commit(DataTypeManagerHandler dtmHandler, DataType dt) {
public static boolean commit(ArchiveManager archiveManager, DataType dt) {
SourceArchive sourceArchive = dt.getSourceArchive();
DataTypeManager sourceDTM = dtmHandler.getDataTypeManager(sourceArchive);
DataTypeManager sourceDTM = archiveManager.getDataTypeManager(sourceArchive);
if (sourceDTM == null) {
return false;
}
@@ -167,14 +166,14 @@ public class DataTypeSynchronizer {
/**
* Updates a single data type in the program to match the associated source data type from the
* archive.
* @param dtmHandler the handler that manages data types
* @param archiveManager the handler that manages data types
* @param dt the data type
* @return true if the update succeeds
*/
public static boolean update(DataTypeManagerHandler dtmHandler, DataType dt) {
public static boolean update(ArchiveManager archiveManager, DataType dt) {
DataTypeManager dataTypeManager = dt.getDataTypeManager();
SourceArchive sourceArchive = dt.getSourceArchive();
DataTypeManager sourceDtm = dtmHandler.getDataTypeManager(sourceArchive);
DataTypeManager sourceDtm = archiveManager.getDataTypeManager(sourceArchive);
if (dataTypeManager == null || sourceDtm == null) {
return false;
}
@@ -275,7 +274,7 @@ public class DataTypeSynchronizer {
}
public static DataTypeSyncState getSyncStatus(DataTypeManagerHandler handler,
public static DataTypeSyncState getSyncStatus(ArchiveManager archiveManager,
DataType dataType) {
DataTypeManager dataTypeManager = dataType.getDataTypeManager();
SourceArchive sourceArchive = dataType.getSourceArchive();
@@ -287,7 +286,7 @@ public class DataTypeSynchronizer {
boolean hasChangedLocally =
dataType.getLastChangeTime() != dataType.getLastChangeTimeInSourceArchive();
DataTypeManager sourceDTM = handler.getDataTypeManager(sourceArchive);
DataTypeManager sourceDTM = archiveManager.getDataTypeManager(sourceArchive);
DataTypeSyncInfo syncInfo = new DataTypeSyncInfo(dataType, sourceDTM);
if (sourceDTM == null) {
return hasChangedLocally ? DataTypeSyncState.COMMIT : DataTypeSyncState.IN_SYNC;
@@ -295,7 +294,7 @@ public class DataTypeSynchronizer {
return syncInfo.getSyncState();
}
public static String getDiffToolTip(DataTypeManagerHandler handler, DataType dataType) {
public static String getDiffToolTip(ArchiveManager archiveManager, DataType dataType) {
DataTypeManager dataTypeManager = dataType.getDataTypeManager();
SourceArchive sourceArchive = dataType.getSourceArchive();
UniversalID dataTypeID = dataType.getUniversalID();
@@ -304,7 +303,7 @@ public class DataTypeSynchronizer {
return null;
}
DataTypeManager sourceDTM = handler.getDataTypeManager(sourceArchive);
DataTypeManager sourceDTM = archiveManager.getDataTypeManager(sourceArchive);
boolean hasChangedLocally =
dataType.getLastChangeTime() != dataType.getLastChangeTimeInSourceArchive();
DataType sourceDT = null;
@@ -26,11 +26,11 @@ import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.GTreeNodeTransferable;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
import ghidra.app.plugin.core.datamgr.archive.ProjectArchive;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.framework.main.datatable.DomainFileContext;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.data.*;
import ghidra.program.model.dtarchive.ProjectDataTypeArchive;
import ghidra.program.model.listing.Program;
public class DataTypesActionContext extends ProgramActionContext implements DomainFileContext {
@@ -90,11 +90,11 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
domainFiles = new ArrayList<DomainFile>();
for (TreePath path : selectionPaths) {
Object lastPathComponent = path.getLastPathComponent();
if (lastPathComponent instanceof ProjectArchiveNode) {
ProjectArchiveNode node = (ProjectArchiveNode) lastPathComponent;
ProjectArchive archive = (ProjectArchive) node.getArchive();
DomainFile originalDomainFile = archive.getDomainFile();
domainFiles.add(originalDomainFile);
if (lastPathComponent instanceof ProjectArchiveNode node) {
DomainFile originalDomainFile = node.getOriginalDomainFile();
if (originalDomainFile != null) {
domainFiles.add(originalDomainFile);
}
}
}
}
@@ -156,4 +156,17 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
return dataTypeNode;
}
public List<ProjectDataTypeArchive> getSelectedProjectArchives() {
List<ProjectDataTypeArchive> list = new ArrayList<>();
List<GTreeNode> selectedNodes = getSelectedNodes();
for (GTreeNode node : selectedNodes) {
if (node instanceof ProjectArchiveNode projectNode) {
ProjectDataTypeArchive archive = projectNode.getArchive();
list.add(archive);
}
}
return list;
}
}
@@ -38,19 +38,18 @@ import generic.theme.GIcon;
import generic.theme.GThemeDefaults.Colors;
import ghidra.app.plugin.core.datamgr.actions.*;
import ghidra.app.plugin.core.datamgr.actions.associate.*;
import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.app.util.ToolTipUtils;
import ghidra.app.util.datatype.DataTypeUrl;
import ghidra.framework.main.datatree.ArchiveProvider;
import ghidra.framework.main.datatree.VersionControlDataTypeArchiveUndoCheckoutAction;
import ghidra.framework.main.projectdata.actions.*;
import ghidra.framework.model.DomainFile;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.data.*;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResolutionPolicy;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.dtarchive.PersistentDataTypeArchive;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
import ghidra.program.model.listing.Program;
import ghidra.util.*;
import ghidra.util.task.SwingUpdateManager;
@@ -172,12 +171,13 @@ public class DataTypesProvider extends ComponentProviderAdapter {
// File group
addLocalAction(new SaveArchiveAction(plugin)); // Archive
addLocalAction(new SaveArchiveAsAction(plugin));
addLocalAction(new CloseArchiveAction(plugin)); // Archive
addLocalAction(new RemoveInvalidArchiveFromProgramAction(plugin)); // Archive
// FileEdit group
addLocalAction(new LockArchiveAction(plugin)); // Archive
addLocalAction(new UnlockArchiveAction(plugin)); // Archive
addLocalAction(new OpenArchiveForEditingAction(plugin)); // Archive
addLocalAction(new CloseArchiveForEditingAction(plugin)); // Archive
addLocalAction(new UndoArchiveTransactionAction(plugin)); // Archive
addLocalAction(new RedoArchiveTransactionAction(plugin)); // Archive
@@ -247,20 +247,6 @@ public class DataTypesProvider extends ComponentProviderAdapter {
private void addVersionControlActions() {
ArchiveProvider archiveProvider = () -> {
TreePath[] selectionPaths = archiveGTree.getSelectionPaths();
List<Archive> selectedArchives = new ArrayList<>();
for (TreePath path : selectionPaths) {
Object lastPathComponent = path.getLastPathComponent();
if (lastPathComponent instanceof ProjectArchiveNode) {
ProjectArchiveNode node = (ProjectArchiveNode) lastPathComponent;
ProjectArchive archive = (ProjectArchive) node.getArchive();
selectedArchives.add(archive);
}
}
return selectedArchives;
};
VersionControlAddAction addAction = new VersionControlAddAction(plugin);
addAction.setEnabled(false);
@@ -274,8 +260,8 @@ public class DataTypesProvider extends ComponentProviderAdapter {
new VersionControlCheckInAction(plugin, archiveGTree);
checkInAction.setEnabled(false);
VersionControlDataTypeArchiveUndoCheckoutAction undoCheckOutAction =
new VersionControlDataTypeArchiveUndoCheckoutAction(plugin, archiveProvider);
ArchiveUndoCheckoutTask undoCheckOutAction =
new ArchiveUndoCheckoutTask(plugin);
undoCheckOutAction.setEnabled(false);
VersionControlShowHistoryAction showHistoryAction =
@@ -572,35 +558,16 @@ public class DataTypesProvider extends ComponentProviderAdapter {
}
private static DataType updateDataType(CategoryPath path, String dataTypeName,
ArchiveNode archiveNode) {
DataTypeManager dataTypeManager = archiveNode.getArchive().getDataTypeManager();
PersistentDataTypeArchive archive) {
if (archive == null) {
return null;
}
DataTypeManager dataTypeManager = archive.getDataTypeManager();
Category category = dataTypeManager.getCategory(path);
return category.getDataType(dataTypeName);
}
private boolean getWriteLock(DataTypeManagerPlugin dataTypePlugin, ArchiveNode archiveNode) {
if (!isOkToLock()) {
return false;
}
GTree tree = dataTypePlugin.getProvider().getGTree();
GTreeState state = tree.getTreeState();
if (!ArchiveUtils.lockArchive((FileArchive) archiveNode.getArchive())) {
return false;
}
tree.restoreTreeState(state);
return true;
}
private boolean needsWriteLock(ArchiveNode archiveNode) {
if (archiveNode instanceof FileArchiveNode) {
FileArchiveNode fileArchiveNode = (FileArchiveNode) archiveNode;
return !fileArchiveNode.hasWriteLock();
}
return false;
}
private boolean isOkToLock() {
private boolean askToOpenArchiveForUpdate() {
return (OptionDialog.showYesNoDialog(archiveGTree, "Open Archive for Edit?",
"Archive file is not modifiable.\nDo you want to open for edit?") == OptionDialog.OPTION_ONE);
}
@@ -623,30 +590,55 @@ public class DataTypesProvider extends ComponentProviderAdapter {
dataType = DataTypeUtils.getBaseDataType(dataType);
CategoryPath path = dataType.getCategoryPath();
String dataTypeName = dataType.getName();
ArchiveNode archiveNode = dataTypeNode.getArchiveNode();
DataTypeStoreNode archiveNode = dataTypeNode.getArchiveNode();
if (archiveNode instanceof ProjectArchiveNode && !archiveNode.isModifiable()) {
ProjectArchiveNode projectArchive = (ProjectArchiveNode) archiveNode;
if (projectArchive.getDomainFile().isReadOnly()) {
Msg.showInfo(getClass(), archiveGTree, "Read-Only Archive",
"You may not edit data type within a read-only project archive.");
}
else {
Msg.showInfo(getClass(), archiveGTree, "Archive Not Checked Out",
"You must checkout this archive before you may edit data types.");
}
if (isUnmodifiableProjectArchive(archiveNode)) {
return;
}
// must get write lock before we can edit
if (needsWriteLock(archiveNode)) {
if (!getWriteLock(plugin, archiveNode)) {
return;
if (archiveNode instanceof FileArchiveNode fileNode) {
FileDataTypeArchive archive = fileNode.getArchive();
if (!archive.isChangeable()) {
if (!askToOpenArchiveForUpdate()) {
return;
}
archive = openForUpdate(archive);
dataType = updateDataType(path, dataTypeName, archive);
}
dataType = updateDataType(path, dataTypeName, archiveNode);
}
if (dataType != null) {
plugin.getEditorManager().edit(dataType);
}
}
private FileDataTypeArchive openForUpdate(FileDataTypeArchive archive) {
GTree tree = plugin.getProvider().getGTree();
GTreeState state = tree.getTreeState();
archive = plugin.getArchiveManager().reopenFileArchive(archive, true);
tree.restoreTreeState(state);
return archive;
}
private boolean isUnmodifiableProjectArchive(DataTypeStoreNode archiveNode) {
if (!(archiveNode instanceof ProjectArchiveNode projectNode)) {
return false;
}
plugin.getEditorManager().edit(dataType);
if (archiveNode.isModifiable()) {
return false;
}
DomainFile domainFile = projectNode.getDomainFile();
DomainFile originalDomainFile = projectNode.getOriginalDomainFile();
if (domainFile.getVersion() == originalDomainFile.getLatestVersion() &&
originalDomainFile.canCheckout()) {
Msg.showInfo(getClass(), archiveGTree, "Archive Not Checked Out",
"You must checkout this archive before you may edit data types.");
}
else {
Msg.showInfo(getClass(), archiveGTree, "Archive Opened Read-Only",
"You may not edit data type within a read-only project archive.");
}
return true;
}
void restore(SaveState saveState) {
@@ -692,55 +684,26 @@ public class DataTypesProvider extends ComponentProviderAdapter {
return archiveGTree;
}
void domainObjectRestored(DataTypeManagerDomainObject domainObject) {
void domainObjectRestored(PersistentDataTypeArchive archive) {
if (archiveGTree == null) {
return; // nothing to update
}
if (domainObject instanceof Program) {
Program program = (Program) domainObject;
Program programInTree = plugin.getProgram(); // may be null
if (program == programInTree) {
DataTypeArchiveGTree gTree = getGTree();
ArchiveNode node = getProgramArchiveNode();
if (node != null) {
GTreeState state = gTree.getTreeState(node);
node.structureChanged();
gTree.restoreTreeState(state);
}
}
}
else if (domainObject instanceof DataTypeArchive) {
DataTypeArchive dataTypeArchive = (DataTypeArchive) domainObject;
DataTypeArchiveGTree gTree = getGTree();
ArchiveNode node = getDataTypeArchiveNode(dataTypeArchive);
if (node != null) {
GTreeState state = gTree.getTreeState(node);
node.structureChanged();
gTree.restoreTreeState(state);
}
DataTypeArchiveGTree gTree = getGTree();
ArchiveNode node = getDataTypeArchiveNode(archive);
if (node != null) {
GTreeState state = gTree.getTreeState(node);
node.structureChanged();
gTree.restoreTreeState(state);
}
}
private ProgramArchiveNode getProgramArchiveNode() {
private ArchiveNode getDataTypeArchiveNode(PersistentDataTypeArchive dataTypeArchive) {
GTreeNode rootNode = getGTree().getModelRoot();
List<GTreeNode> children = rootNode.getChildren();
for (GTreeNode node : children) {
if (node instanceof ProgramArchiveNode programNode) {
return programNode;
}
}
return null;
}
private ArchiveNode getDataTypeArchiveNode(DataTypeArchive dataTypeArchive) {
GTreeNode rootNode = getGTree().getModelRoot();
List<GTreeNode> children = rootNode.getChildren();
for (GTreeNode node : children) {
ArchiveNode archiveNode = (ArchiveNode) node;
Archive archive = archiveNode.getArchive();
if (archive instanceof ProjectArchive) {
ProjectArchive projectArchive = (ProjectArchive) archive;
if (projectArchive.getDataTypeManager() == dataTypeArchive.getDataTypeManager()) {
if (node instanceof ArchiveNode archiveNode) {
PersistentDataTypeArchive archive = archiveNode.getArchive();
if (archive == dataTypeArchive) {
return archiveNode;
}
}
@@ -774,7 +737,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
Category category = dataTypeManager.getCategory(dataType.getCategoryPath());
ArchiveRootNode rootNode = (ArchiveRootNode) gTree.getViewRoot();
ArchiveNode archiveNode = rootNode.getNodeForManager(dataTypeManager);
DataTypeStoreNode archiveNode = rootNode.getNodeForManager(dataTypeManager);
if (archiveNode == null) {
plugin.setStatus("Cannot find archive '" + dataTypeManager.getName() + "'. It may " +
"be filtered out of view or may have been closed (Data Type Manager)");
@@ -824,7 +787,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
}
ArchiveRootNode rootNode = (ArchiveRootNode) gTree.getViewRoot();
ArchiveNode archiveNode = rootNode.getNodeForManager(dataTypeManager);
DataTypeStoreNode archiveNode = rootNode.getNodeForManager(dataTypeManager);
if (archiveNode == null) {
plugin.setStatus("Cannot find archive '" + dataTypeManager.getName() + "'. It may " +
"be filtered out of view or may have been closed (Data Type Manager)");
@@ -924,8 +887,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
}
private void restoreProgramTreeState(ProgramArchiveNode programNode) {
ProgramArchive programArchive = (ProgramArchive) programNode.getArchive();
Program program = programArchive.getProgram();
Program program = programNode.getProgram();
long id = program.getUniqueProgramID();
TreePath selectedPath = programTreeState.get(id);
if (selectedPath == null) {
@@ -946,8 +908,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
// of the program node, so only save the path if there is a single selection. This will
// be helpful in the case that the user was working with a single data type in the program.
//
ProgramArchive programArchive = (ProgramArchive) programNode.getArchive();
Program program = programArchive.getProgram();
Program program = programNode.getProgram();
long id = program.getUniqueProgramID();
GTreeState state = archiveGTree.getTreeState();
List<TreePath> paths = state.getSelectedPaths();
@@ -987,16 +948,11 @@ public class DataTypesProvider extends ComponentProviderAdapter {
clearDataTypePreview();
}
void archiveClosed(DataTypeManager dtm) {
dataTypeManagerChanged(dtm);
void archiveClosed(PersistentDataTypeArchive archive) {
archiveChanged(archive.getName());
}
void archiveChanged(Archive archive) {
DataTypeManager dtm = archive.getDataTypeManager();
dataTypeManagerChanged(dtm);
}
private void dataTypeManagerChanged(DataTypeManager dtm) {
void archiveChanged(String name) {
if (lastPreviewNode == null || !(lastPreviewNode instanceof DataTypeNode)) {
return;
@@ -1005,9 +961,10 @@ public class DataTypesProvider extends ComponentProviderAdapter {
DataTypeNode dtNode = (DataTypeNode) lastPreviewNode;
DataType dt = dtNode.getDataType();
DataTypeManager dtManager = dt.getDataTypeManager();
// note: compare using name; an equality check will fail if the manager is reloaded
if (dtm.getName().equals(dtManager.getName())) {
// notes: the archive name and the datatypeManager name are the same. If the archive
// changed because it was closed, then its datatype manager will be null. So just
// compare the datatype's manager's name with the archive's name.
if (name.equals(dtManager.getName())) {
lastPreviewNode = null;
}
}
@@ -1016,9 +973,8 @@ public class DataTypesProvider extends ComponentProviderAdapter {
ArchiveRootNode rootNode = (ArchiveRootNode) archiveGTree.getModelRoot();
List<GTreeNode> allChildren = rootNode.getChildren();
for (GTreeNode node : allChildren) {
ArchiveNode archiveNode = (ArchiveNode) node;
if (archiveNode.getArchive() instanceof ProgramArchive) {
archiveNode.nodeChanged();
if (node instanceof ProgramArchiveNode programNode) {
programNode.nodeChanged();
return;
}
}
@@ -1087,14 +1043,14 @@ public class DataTypesProvider extends ComponentProviderAdapter {
private class ProgramNodeUpdateListener implements ArchiveRootNodeListener {
@Override
public void archiveNodeAdded(ArchiveNode node) {
public void archiveNodeAdded(DataTypeStoreNode node) {
if (node instanceof ProgramArchiveNode programNode) {
restoreProgramTreeState(programNode);
}
}
@Override
public void archiveNodeRemoved(ArchiveNode node) {
public void archiveNodeRemoved(DataTypeStoreNode node) {
if (node instanceof ProgramArchiveNode programNode) {
saveProgramTreeState(programNode);
}
@@ -1,239 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import javax.swing.SwingUtilities;
import docking.widgets.OptionDialog;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
class OpenDomainFileTask extends Task {
private DomainFile domainFile;
private int version;
private DataTypeManagerPlugin dtmPlugin;
private DataTypeManagerHandler dtmHandler;
private PluginTool tool;
private DataTypeArchive dtArchive = null;
OpenDomainFileTask(DomainFile domainFile, int version, PluginTool tool,
DataTypeManagerPlugin dtmPlugin) {
super("Open Project Data Type Archive", true, true, true);
this.domainFile = domainFile;
this.dtmPlugin = dtmPlugin;
this.dtmHandler = dtmPlugin.getDataTypeManagerHandler();
this.tool = tool;
this.version = version;
}
DataTypeArchive getArchive() {
return dtArchive;
}
/* (non-Javadoc)
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
*/
@Override
public void run(TaskMonitor monitor) {
if (isFileOpen()) {
return;
}
boolean associateWithOriginalDomainFile = true;
if (version != DomainFile.DEFAULT_VERSION) {
openReadOnlyFile(monitor);
associateWithOriginalDomainFile = false;
}
else if (domainFile.isReadOnly()) {
openReadOnlyFile(monitor);
}
else if (domainFile.isVersioned() && !domainFile.isCheckedOut()) {
openReadOnlyFile(monitor);
}
else {
openUnversionedFile(monitor);
}
if (dtArchive != null) {
openFileInTree(associateWithOriginalDomainFile);
dtArchive.release(this);
}
}
private boolean isFileOpen() {
List<Archive> dtArchiveList = dtmHandler.getAllArchives();
for (Archive archive : dtArchiveList) {
if (archive instanceof ProjectArchive) {
ProjectArchive projectArchive = (ProjectArchive) archive;
DomainFile archiveDomainFile = projectArchive.getDomainFile();
if (filesMatch(domainFile, archiveDomainFile)) {
// archive = projectArchive;
// dtmHandler.open // TODO
return true;
}
}
}
return false;
}
private boolean filesMatch(DomainFile file1, DomainFile file2) {
if (!file1.getPathname().equals(file2.getPathname())) {
return false;
}
if (file1.isCheckedOut() != file2.isCheckedOut()) {
return false;
}
if (!SystemUtilities.isEqual(file1.getProjectLocator(), file2.getProjectLocator())) {
return false;
}
int otherVersion = file2.isReadOnly() ? file2.getVersion() : -1;
return version == otherVersion;
}
/**
* Open archive in an immutable fashion. Unlike ProgramDB, we do not want to
* allow upgrade or modification of a read-only archve (e.g., not-checked-out).
* @param monitor task monitor
*/
private void openReadOnlyFile(TaskMonitor monitor) {
String fileDescr =
((version != DomainFile.DEFAULT_VERSION) ? "version " + version + " of " : "") +
domainFile.getName();
String contentType = null;
try {
monitor.setMessage("Opening " + fileDescr);
contentType = domainFile.getContentType();
dtArchive =
(DataTypeArchive) domainFile.getImmutableDomainObject(this, version, monitor);
}
catch (CancelledException e) {
// we don't care, the task has been canceled
}
catch (IOException e) {
if (domainFile.isVersioned() && domainFile.isInWritableProject()) {
ClientUtil.handleException(AppInfo.getActiveProject().getRepository(), e,
"Get Versioned Object", null);
}
else {
Msg.showError(this, null, "Project Archive Open Error",
"Error occurred while opening " + fileDescr, e);
}
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(tool.getToolFrame(), domainFile.getName(),
contentType, "Open", false, e);
}
}
private void openUnversionedFile(TaskMonitor monitor) {
monitor.setMessage("Opening " + domainFile.getName());
String contentType = null;
try {
final boolean recoverFile = isRecoveryOK(domainFile);
contentType = domainFile.getContentType();
try {
dtArchive =
(DataTypeArchive) domainFile.getDomainObject(this, false, recoverFile, monitor);
}
catch (VersionException e) {
if (VersionExceptionHandler.isUpgradeOK(null, domainFile, "Open", e)) {
dtArchive = (DataTypeArchive) domainFile.getDomainObject(this, true,
recoverFile, monitor);
}
}
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
"Open", false, e);
}
catch (CancelledException e) {
// we don't care, the task has been canceled
}
catch (Exception e) {
if (domainFile.isInWritableProject() && (e instanceof IOException)) {
RepositoryAdapter repo = domainFile.getParent().getProjectData().getRepository();
ClientUtil.handleException(repo, e, "Open File", null);
}
else {
Msg.showError(this, null, "Error Opening " + domainFile.getName(),
"Opening data type archive failed.\n" + e.getMessage());
}
}
}
private boolean isRecoveryOK(final DomainFile dfile)
throws InterruptedException, InvocationTargetException {
final boolean[] recoverFile = new boolean[] { false };
if (dfile.isInWritableProject() && dfile.canRecover()) {
Runnable r = () -> {
int option = OptionDialog.showYesNoDialog(null, "Crash Recovery Data Found",
"<html>" + HTMLUtilities.escapeHTML(dfile.getName()) + " has crash data.<br>" +
"Would you like to recover unsaved changes?");
recoverFile[0] = (option == OptionDialog.OPTION_ONE);
};
SwingUtilities.invokeAndWait(r);
}
return recoverFile[0];
}
private void openFileInTree(boolean associatedWithOriginalDomainFile) {
DataTypesProvider provider = dtmPlugin.getProvider();
GTree tree = provider.getGTree();
DataTypeManagerHandler manager = dtmPlugin.getDataTypeManagerHandler();
DomainFile df = associatedWithOriginalDomainFile ? domainFile : dtArchive.getDomainFile();
Archive archive = manager.openArchive(dtArchive, df);
GTreeNode node = getNodeForArchive(tree, archive);
if (node != null) {
tree.setSelectedNode(node);
}
}
private GTreeNode getNodeForArchive(GTree tree, Archive archive) {
GTreeNode rootNode = tree.getModelRoot();
for (GTreeNode node : rootNode.getChildren()) {
if (node instanceof ArchiveNode) {
ArchiveNode archiveNode = (ArchiveNode) node;
if (archiveNode.getArchive() == archive) {
return archiveNode;
}
}
}
return null;
}
}
@@ -0,0 +1,117 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr;
import java.io.FileNotFoundException;
import java.io.IOException;
import docking.widgets.OptionDialog;
import generic.jar.ResourceFile;
import ghidra.app.services.Upgrade;
import ghidra.program.database.dtarchive.DataTypeArchiveFactory;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
import ghidra.util.Msg;
import ghidra.util.VersionExceptionHandler;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
class OpenFileArchiveTask extends Task {
private ResourceFile file;
private FileDataTypeArchive archive = null;
private Object consumer;
private Upgrade upgradeStrategy;
private boolean openForUpdate;
OpenFileArchiveTask(ResourceFile file, boolean openForUpdate,
Upgrade upgradeStrategy, Object consumer) {
super("Opening File DataType Archive " + file.getName(), false, false, true);
this.file = file;
this.openForUpdate = openForUpdate;
this.upgradeStrategy = upgradeStrategy;
this.consumer = consumer;
}
FileDataTypeArchive getArchive() {
return archive;
}
@Override
public void run(TaskMonitor monitor) {
try {
archive = openArchive(monitor);
}
catch (FileNotFoundException e) {
Msg.showError(this, null, "Open File Archive Failed",
file.getAbsolutePath() + " not found!");
}
catch (IOException e) {
Msg.showError(this, null, "Open File Archive Failed",
e.getMessage() + ": " + file.getName());
}
catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, file.getName(), "Archive", "open", false,
e);
}
catch (CancelledException e) {
// user cancelled, nothing to report
}
}
public FileDataTypeArchive openArchive(TaskMonitor monitor)
throws VersionException, IOException, CancelledException {
if (!openForUpdate) {
return DataTypeArchiveFactory.openReadOnly(file, consumer, monitor);
}
try {
return DataTypeArchiveFactory.openForUpdate(file, false, consumer, monitor);
}
catch (VersionException e) {
if (shouldUpgrade(e)) {
return DataTypeArchiveFactory.openForUpdate(file, true, consumer, monitor);
}
throw e;
}
}
private boolean shouldUpgrade(VersionException e) {
if (!e.isUpgradable()) {
return false;
}
switch (upgradeStrategy) {
case YES:
return true;
case NO:
return false;
case ASK:
default:
return askToUpgrade();
}
}
private boolean askToUpgrade() {
return OptionDialog.showOptionDialog(null,
"Upgrade File Archive: " + file.getName(),
"File archive is an older version.\n" +
"Do you want to upgrade it to the latest version?",
"Upgrade",
OptionDialog.QUESTION_MESSAGE) == OptionDialog.YES_OPTION;
}
}
@@ -0,0 +1,162 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr;
import java.io.IOException;
import docking.widgets.OptionDialog;
import ghidra.app.services.Recover;
import ghidra.app.services.Upgrade;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.DomainFile;
import ghidra.program.database.dtarchive.DataTypeArchiveFactory;
import ghidra.program.model.dtarchive.ProjectDataTypeArchive;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
class OpenProjectArchiveTask extends Task {
private DomainFile file;
private int version;
private ProjectDataTypeArchive archive = null;
private Object consumer;
private Recover recoverStrategy;
private Upgrade upgradeStrategy;
OpenProjectArchiveTask(DomainFile file, int version, Upgrade upgradeStrategy,
Recover recoverStrategy, Object consumer) {
super("Open Project Data Type Archive", true, true, true);
this.file = file;
this.upgradeStrategy = upgradeStrategy;
this.recoverStrategy = recoverStrategy;
this.consumer = consumer;
this.version = version;
}
ProjectDataTypeArchive getArchive() {
return archive;
}
@Override
public void run(TaskMonitor monitor) {
monitor.setMessage(getDescription());
try {
archive = openArchive(monitor);
}
catch (VersionException e) {
String contentType = file.getContentType();
VersionExceptionHandler.showVersionError(null, file.getName(),
contentType, "Open", false, e);
}
catch (CancelledException e) {
// do nothing, user cancelled
}
catch (IOException e) {
if (file.isVersioned() && file.isInWritableProject()) {
ClientUtil.handleException(AppInfo.getActiveProject().getRepository(), e,
"Get Versioned Object", null);
}
else {
Msg.showError(this, null, "Project Archive Open Error",
"Error occurred while opening " + file.getName(), e);
}
}
}
private String getDescription() {
String description = "Opening " + file.getName();
if (version != DomainFile.DEFAULT_VERSION) {
description += " (version " + version + ")";
}
return description;
}
public ProjectDataTypeArchive openArchive(TaskMonitor monitor)
throws VersionException, IOException, CancelledException {
if (shouldOpenImmutable(file)) {
return DataTypeArchiveFactory.openReadOnly(file, version, consumer, monitor);
}
boolean okToRecover = shouldRecover();
try {
return DataTypeArchiveFactory.openForUpdate(file, consumer, false, okToRecover,
monitor);
}
catch (VersionException e) {
if (shouldUpgrade(e)) {
return DataTypeArchiveFactory.openForUpdate(file, consumer, true, okToRecover,
monitor);
}
throw e;
}
}
private boolean shouldUpgrade(VersionException e) {
if (!e.isUpgradable()) {
return false;
}
switch (upgradeStrategy) {
case YES:
return true;
case NO:
return false;
case ASK:
default:
return VersionExceptionHandler.isUpgradeOK(null, file, "Open File Archive", e);
}
}
private boolean shouldOpenImmutable(DomainFile domainFile) {
if (version != DomainFile.DEFAULT_VERSION) {
return true;
}
if (domainFile.isReadOnly()) {
return true;
}
return domainFile.isVersioned() && !domainFile.isCheckedOut();
}
private boolean shouldRecover() {
if (!file.isInWritableProject()) {
return false;
}
switch (recoverStrategy) {
case YES:
return true;
case NO:
return false;
case ASK:
default:
return askToRecover();
}
}
private boolean askToRecover() {
if (!file.canRecover()) {
return false;
}
int option = OptionDialog.showYesNoDialog(null, "Crash Recovery Data Found",
"<html>" + HTMLUtilities.escapeHTML(file.getName()) + " has crash data.<br>" +
"Would you like to recover unsaved changes?");
return (option == OptionDialog.OPTION_ONE);
}
}
@@ -0,0 +1,137 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr;
import java.io.IOException;
import java.util.List;
import docking.widgets.OptionDialog;
import ghidra.app.util.HelpTopics;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.main.DataTreeDialogType;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.dtarchive.ProjectDataTypeArchive;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskLauncher;
public class ProjectArchiveSaveAsDialog extends DataTreeDialog {
private static final String CONTENT_NAME = "Data Type Archive";
private ProjectDataTypeArchive archive;
private PluginTool tool;
public ProjectArchiveSaveAsDialog(PluginTool tool, ProjectDataTypeArchive archive) {
super(null, "Save Project Archive As", DataTreeDialogType.SAVE);
this.tool = tool;
this.archive = archive;
setHelpLocation(new HelpLocation(HelpTopics.PROGRAM, "Save_As_File"));
}
@Override
protected void okCallback() {
DomainFolder folder = getDomainFolder();
String name = getNameText();
if (name.length() == 0) {
setStatusText("Please enter a name");
return;
}
if (folder == null) {
setStatusText("Please select a folder");
return;
}
DomainFile file = folder.getFile(name);
if (file != null) {
setStatusText("Choose a name that doesn't exist");
return;
}
close();
doSaveAs();
}
private void doSaveAs() {
if (!getSaveAsLock(archive)) {
return;
}
try {
DomainFolder folder = getDomainFolder();
String newName = getNameText();
tool.prepareToSave(archive);
TaskLauncher.launchModal("Save As", monitor -> {
try {
folder.createFile(newName, archive, monitor);
}
catch (InvalidNameException e) {
Msg.showError(this, null, "Invalid Name", e.getMessage(), e);
}
catch (CancelledException e) {
// user cancelled, nothing to do
}
catch (IOException e) {
Msg.showError(this, null, "I/O error ", e.getMessage(), e);
}
});
}
finally {
archive.unlock();
}
}
private boolean getSaveAsLock(DomainObject domainObject) {
if (!domainObject.lock(null)) {
String title = "Save " + CONTENT_NAME + " As (Busy)";
StringBuffer buf = new StringBuffer();
buf.append("The " + CONTENT_NAME +
" is currently being modified by the following actions/tasks:\n \n");
TransactionInfo t = domainObject.getCurrentTransactionInfo();
List<String> list = t.getOpenSubTransactions();
for (String element : list) {
buf.append("\n ");
buf.append(element);
}
buf.append("\n \n");
buf.append(
"WARNING! The above task(s) should be cancelled before attempting a Save As...\n");
buf.append("Only proceed if unable to cancel them.\n \n");
buf.append(
"If you click 'Save Archive As (Rollback)' {recommended}, all changes made\n");
buf.append("by these tasks, as well as any other overlapping task, will be LOST!\n");
buf.append(
"If you click 'Save As (As Is)', the archive will be saved in its current\n");
buf.append("state which may contain some incomplete data.\n");
buf.append("Any forced save may also result in subsequent transaction errors while\n");
buf.append("the above tasks remain active.\n ");
int result = OptionDialog.showOptionDialog(null, title, buf.toString(),
"Save Archive As (Rollback)!", "Save Archive As (As Is)!",
OptionDialog.WARNING_MESSAGE);
if (result == OptionDialog.OPTION_ONE) {
domainObject.forceLock(true, "Save Archive As");
return true;
}
else if (result == OptionDialog.OPTION_TWO) {
domainObject.forceLock(false, "Save Archive As");
return true;
}
return false;
}
return true;
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -27,8 +27,7 @@ import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.StandAloneDataTypeManager;
import ghidra.program.model.dtarchive.PersistentDataTypeArchive;
public abstract class AbstractUndoRedoArchiveTransactionAction extends DockingAction {
@@ -62,28 +61,28 @@ public abstract class AbstractUndoRedoArchiveTransactionAction extends DockingAc
}
TreePath[] selectionPaths = getSelectionPaths(context);
return getModifiableProjectOrFileDTM(selectionPaths) != null;
return getModifiableProjectOrFileArchive(selectionPaths) != null;
}
/**
* Determine if the corresponding undo/redo can be performed
* @param dtm archive datatype manager
* @param archive datatype archive
* @return true if action can be performed on archive
*/
abstract protected boolean canExecute(StandAloneDataTypeManager dtm);
abstract protected boolean canExecute(PersistentDataTypeArchive archive);
/**
* Determine the next undo/redo transaction name
* @param dtm archive datatype manager
* @param archive datatype archive
* @return next undo/redo transaction name
*/
abstract protected String getNextName(StandAloneDataTypeManager dtm);
abstract protected String getNextName(PersistentDataTypeArchive archive);
/**
* Execute the undo/redo operation on the specified archive datatype manager.
* @param dtm archive datatype manager
* Execute the undo/redo operation on the specified archive.
* @param archive datatype archive
*/
abstract protected void execute(StandAloneDataTypeManager dtm);
abstract protected void execute(PersistentDataTypeArchive archive);
@Override
public boolean isEnabledForContext(ActionContext context) {
@@ -92,9 +91,9 @@ public abstract class AbstractUndoRedoArchiveTransactionAction extends DockingAc
}
TreePath[] selectionPaths = getSelectionPaths(context);
StandAloneDataTypeManager dtm = getModifiableProjectOrFileDTM(selectionPaths);
if (dtm != null && canExecute(dtm)) {
setPopupMenuData(getMenuData(getNextName(dtm)));
PersistentDataTypeArchive dta = getModifiableProjectOrFileArchive(selectionPaths);
if (dta != null && canExecute(dta)) {
setPopupMenuData(getMenuData(getNextName(dta)));
return true;
}
setPopupMenuData(getMenuData(null));
@@ -109,9 +108,9 @@ public abstract class AbstractUndoRedoArchiveTransactionAction extends DockingAc
}
TreePath[] selectionPaths = getSelectionPaths(context);
StandAloneDataTypeManager dtm = getModifiableProjectOrFileDTM(selectionPaths);
if (dtm != null && canExecute(dtm)) {
execute(dtm);
PersistentDataTypeArchive dta = getModifiableProjectOrFileArchive(selectionPaths);
if (dta != null && canExecute(dta)) {
execute(dta);
}
}
@@ -122,7 +121,7 @@ public abstract class AbstractUndoRedoArchiveTransactionAction extends DockingAc
return selectionPaths;
}
private StandAloneDataTypeManager getModifiableProjectOrFileDTM(TreePath[] selectionPaths) {
private PersistentDataTypeArchive getModifiableProjectOrFileArchive(TreePath[] selectionPaths) {
// only valid if single file or project archive node is selected
if (selectionPaths.length != 1) {
return null;
@@ -140,9 +139,9 @@ public abstract class AbstractUndoRedoArchiveTransactionAction extends DockingAc
ArchiveNode archiveNode = (ArchiveNode) node;
if (archiveNode.isModifiable()) {
DataTypeManager dtm = archiveNode.getArchive().getDataTypeManager();
if (dtm instanceof StandAloneDataTypeManager archiveDtm) {
return archiveDtm;
PersistentDataTypeArchive archive = archiveNode.getArchive();
if (archive instanceof PersistentDataTypeArchive dta) {
return dta;
}
}
return null;
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -78,8 +78,8 @@ public class CaptureFunctionDataTypesAction extends DockingAction {
public void actionPerformed(ActionContext context) {
GTree gTree = (GTree) context.getContextObject();
TreePath selectionPath = gTree.getSelectionPath();
ArchiveNode node = (ArchiveNode) selectionPath.getLastPathComponent();
if (!node.getArchive().isModifiable()) {
DataTypeStoreNode node = (DataTypeStoreNode) selectionPath.getLastPathComponent();
if (!node.getDataTypeStore().isChangeable()) {
informNotModifiable(node);
return;
}
@@ -89,7 +89,7 @@ public class CaptureFunctionDataTypesAction extends DockingAction {
if (currentSelection == null || currentSelection.isEmpty()) {
currentSelection = program.getMemory();
}
final DataTypeManager manager = node.getArchive().getDataTypeManager();
final DataTypeManager manager = node.getDataTypeManager();
final PluginTool tool = plugin.getTool();
CaptureFunctionDataTypesCmd cmd =
new CaptureFunctionDataTypesCmd(manager, currentSelection,
@@ -105,7 +105,7 @@ public class CaptureFunctionDataTypesAction extends DockingAction {
tool.executeBackgroundCommand(cmd, program);
}
private void informNotModifiable(ArchiveNode node) {
private void informNotModifiable(DataTypeStoreNode node) {
String message;
if (node instanceof ProgramArchiveNode) {
message = "The program \"" + node.getName() + "\" isn't modifiable.";
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,8 +15,6 @@
*/
package ghidra.app.plugin.core.datamgr.actions;
import java.io.IOException;
import javax.swing.tree.TreePath;
import docking.ActionContext;
@@ -26,13 +24,11 @@ import docking.widgets.OptionDialog;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import generic.theme.GThemeDefaults.Colors.Messages;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.framework.model.DomainFile;
import ghidra.framework.store.LockException;
import ghidra.program.model.data.StandAloneDataTypeManager;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.dtarchive.PersistentDataTypeArchive;
import ghidra.program.model.dtarchive.ProjectDataTypeArchive;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
@@ -71,8 +67,7 @@ public class ClearArchiveArchitectureAction extends DockingAction {
return false;
}
ArchiveNode archiveNode = (ArchiveNode) node;
StandAloneDataTypeManager dtm =
(StandAloneDataTypeManager) archiveNode.getArchive().getDataTypeManager();
DataTypeManager dtm = archiveNode.getDataTypeManager();
return dtm.getProgramArchitectureSummary() != null && dtm.isUpdatable();
}
@@ -91,7 +86,7 @@ public class ClearArchiveArchitectureAction extends DockingAction {
if (node instanceof ProjectArchiveNode) {
ProjectArchiveNode paNode = (ProjectArchiveNode) node;
ProjectArchive pa = (ProjectArchive) paNode.getArchive();
ProjectDataTypeArchive pa = paNode.getArchive();
if (!pa.hasExclusiveAccess()) {
Msg.showError(this, null, "Clear Program Architecture Failed",
"Clearing program-architecture on Project Archive requires exclusive checkout.");
@@ -100,10 +95,9 @@ public class ClearArchiveArchitectureAction extends DockingAction {
}
ArchiveNode archiveNode = (ArchiveNode) node;
StandAloneDataTypeManager dtm =
(StandAloneDataTypeManager) archiveNode.getArchive().getDataTypeManager();
PersistentDataTypeArchive archive = archiveNode.getArchive();
if (dtm.isChanged()) {
if (archive.isChanged()) {
if (OptionDialog.OPTION_ONE != OptionDialog.showOptionDialogWithCancelAsDefaultButton(
null, "Save Archive Changes",
"Archive has unsaved changes which must be saved before continuing." +
@@ -111,20 +105,14 @@ public class ClearArchiveArchitectureAction extends DockingAction {
"Save")) {
return;
}
try {
archiveNode.getArchive().save();
}
catch (IOException e) {
Msg.showError(this, null, "Save Archive Failed",
"Failed to save changes for Archive: " + dtm.getName() + "\n" + e.getMessage());
return;
}
ArchiveManager archiveManager = plugin.getArchiveManager();
archiveManager.save(archive);
}
// TODO: Update message indicating that custom storage specification will not be
// retained/permitted (once supported)
String msg = "<html>Clear program-architecture for Archive?<BR><font color=\"" +
Messages.NORMAL + "\">" + dtm.getPath() +
Messages.NORMAL + "\">" + archive.getPath() +
"</font><BR> <BR>Archive will revert to using default data organization.";
int response = OptionDialog.showOptionDialogWithCancelAsDefaultButton(null,
"Confirm Clearing Archive Architecture", msg, "Clear Architecture",
@@ -133,25 +121,23 @@ public class ClearArchiveArchitectureAction extends DockingAction {
return;
}
new TaskLauncher(new ClearProgramArchitectureTask(archiveNode.getArchive(), dtm));
new TaskLauncher(new ClearProgramArchitectureTask(archive));
}
private class ClearProgramArchitectureTask extends Task {
private final Archive archive;
private final StandAloneDataTypeManager dtm;
private final PersistentDataTypeArchive archive;
public ClearProgramArchitectureTask(Archive archive, StandAloneDataTypeManager dtm) {
public ClearProgramArchitectureTask(PersistentDataTypeArchive archive) {
super("Clearing Program-Architecture for Archive", true, false, true, false);
this.archive = archive;
this.dtm = dtm;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
boolean success = false;
try {
dtm.clearProgramArchitecture(monitor);
archive.clearProgramArchitecture(monitor);
success = true;
}
catch (CancelledException e) {
@@ -159,25 +145,13 @@ public class ClearArchiveArchitectureAction extends DockingAction {
}
catch (Exception e) {
Msg.showError(this, null, "Archive Update Failed",
"Failed to clear program-architecture for Archive: " + dtm.getName() + "\n" +
"Failed to clear program-architecture for Archive: " + archive.getName() +
"\n" +
e.getMessage());
}
finally {
if (!success) {
if (archive instanceof FileArchive) {
try {
((FileArchive) archive).releaseWriteLock();
((FileArchive) archive).acquireWriteLock();
}
catch (LockException | IOException e) {
archive.close();
}
}
else { // if (archive instanceof ProjectArchive) {
archive.close();
DomainFile df = ((ProjectArchive) archive).getDomainFile();
plugin.openArchive(df);
}
plugin.getArchiveManager().reopenArchive(archive);
}
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,19 +18,15 @@ package ghidra.app.plugin.core.datamgr.actions;
import java.util.ArrayList;
import java.util.List;
import javax.swing.tree.TreePath;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
import ghidra.app.plugin.core.datamgr.archive.Archive;
import ghidra.app.plugin.core.datamgr.archive.ArchiveUtils;
import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.editor.DataTypeEditorManager;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.dtarchive.PersistentDataTypeArchive;
public class CloseArchiveAction extends DockingAction {
@@ -42,28 +38,25 @@ public class CloseArchiveAction extends DockingAction {
setPopupMenuData(new MenuData(new String[] { "Close Archive" }, null, "File"));
setDescription("Closes a data type archive and removes it from the tool "
+ "(does not affect program file associations).");
setDescription("Closes a data type archive and removes it from the tool " +
"(does not affect program file associations).");
setEnabled(true);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof DataTypesActionContext)) {
if (!(context instanceof DataTypesActionContext dtac)) {
return false;
}
Object contextObject = context.getContextObject();
GTree gtree = (GTree) contextObject;
TreePath[] selectionPaths = gtree.getSelectionPaths();
List<GTreeNode> selectedNodes = dtac.getSelectedNodes();
if (selectionPaths.length == 0) {
if (selectedNodes.isEmpty()) {
return false;
}
for (TreePath path : selectionPaths) {
GTreeNode node = (GTreeNode) path.getLastPathComponent();
for (GTreeNode node : selectedNodes) {
if (!(node instanceof FileArchiveNode) && !(node instanceof InvalidArchiveNode) &&
!(node instanceof ProjectArchiveNode)) {
return false;
@@ -74,38 +67,40 @@ public class CloseArchiveAction extends DockingAction {
@Override
public void actionPerformed(ActionContext context) {
GTree gtree = (GTree) context.getContextObject();
TreePath[] selectionPaths = gtree.getSelectionPaths();
DataTypeEditorManager editorManager = plugin.getEditorManager();
List<Archive> archives = new ArrayList<Archive>();
for (TreePath path : selectionPaths) {
Object pathComponent = path.getLastPathComponent();
if (pathComponent instanceof InvalidArchiveNode) {
InvalidArchiveNode invalidArchiveNode = (InvalidArchiveNode) pathComponent;
Archive archive = invalidArchiveNode.getArchive();
archive.close();
continue;
}
DataTypesActionContext dtac = (DataTypesActionContext) context;
ArchiveManager archiveManager = plugin.getArchiveManager();
Archive archive = null;
Object node = path.getLastPathComponent();
if (node instanceof ArchiveNode) {
ArchiveNode archiveNode = (ArchiveNode) node;
archive = archiveNode.getArchive();
List<PersistentDataTypeArchive> archives = new ArrayList<>();
List<GTreeNode> selectedNodes = dtac.getSelectedNodes();
for (GTreeNode node : selectedNodes) {
if (node instanceof InvalidArchiveNode invalidNode) {
archiveManager.closeInvalidArchive(invalidNode.getInvalidArchive());
}
if (archive != null) {
else if (node instanceof ArchiveNode archiveNode) {
PersistentDataTypeArchive archive = archiveNode.getArchive();
archives.add(archive);
if (!editorManager.checkEditors(archive.getDataTypeManager(), true)) {
return;
}
}
}
if (ArchiveUtils.canClose(archives, gtree)) {
for (Archive archive : archives) {
editorManager.dismissEditors(archive.getDataTypeManager());
archive.close();
if (checkEditors(archives)) {
return;
}
for (PersistentDataTypeArchive archive : archives) {
if (!plugin.resolveModifiedArchive(archive)) {
return;
}
archiveManager.closeArchive(archive);
}
}
private boolean checkEditors(List<PersistentDataTypeArchive> archives) {
DataTypeEditorManager editorManager = plugin.getEditorManager();
for (PersistentDataTypeArchive archive : archives) {
DataTypeManager dtm = archive.getDataTypeManager();
if (!editorManager.checkEditors(dtm, true)) {
return true;
}
}
return false;
}
}
@@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,31 +15,28 @@
*/
package ghidra.app.plugin.core.datamgr.actions;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
import ghidra.app.plugin.core.datamgr.archive.ArchiveUtils;
import ghidra.app.plugin.core.datamgr.archive.FileArchive;
import ghidra.app.plugin.core.datamgr.editor.DataTypeEditorManager;
import ghidra.app.plugin.core.datamgr.tree.FileArchiveNode;
import ghidra.program.model.data.DataTypeManager;
import ghidra.util.Msg;
import java.awt.Component;
import java.io.IOException;
import javax.swing.tree.TreePath;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.tree.*;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.editor.DataTypeEditorManager;
import ghidra.app.plugin.core.datamgr.tree.FileArchiveNode;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.dtarchive.FileDataTypeArchive;
public class UnlockArchiveAction extends DockingAction {
public static final String ACTION_NAME = "Unlock Archive";
/**
* Action to set an archive to be read only
*/
public class CloseArchiveForEditingAction extends DockingAction {
public static final String ACTION_NAME = "Close Archive For Editing";
private final DataTypeManagerPlugin plugin;
public UnlockArchiveAction(DataTypeManagerPlugin plugin) {
public CloseArchiveForEditingAction(DataTypeManagerPlugin plugin) {
super(ACTION_NAME, plugin.getName());
this.plugin = plugin;
@@ -89,7 +85,7 @@ public class UnlockArchiveAction extends DockingAction {
// only valid if all selected paths are file archives
for (TreePath path : selectionPaths) {
GTreeNode node = (GTreeNode) path.getLastPathComponent();
if (hasWriteLock((FileArchiveNode) node)) {
if (isModifiable((FileArchiveNode) node)) {
return true;
}
}
@@ -103,9 +99,9 @@ public class UnlockArchiveAction extends DockingAction {
return selectionPaths;
}
private boolean hasWriteLock(FileArchiveNode fileArchiveNode) {
FileArchive archive = (FileArchive) fileArchiveNode.getArchive();
return archive.hasWriteLock();
private boolean isModifiable(FileArchiveNode fileArchiveNode) {
FileDataTypeArchive archive = fileArchiveNode.getArchive();
return archive.isChangeable();
}
@Override
@@ -113,48 +109,22 @@ public class UnlockArchiveAction extends DockingAction {
GTree gTree = (GTree) context.getContextObject();
TreePath[] selectionPaths = gTree.getSelectionPaths();
GTreeState treeState = gTree.getTreeState();
DataTypeEditorManager editorManager = plugin.getEditorManager();
for (TreePath path : selectionPaths) {
FileArchiveNode node = (FileArchiveNode) path.getLastPathComponent();
FileArchive archive = (FileArchive) node.getArchive();
if (!canReleaseLock(archive, gTree)) {
continue;
}
FileDataTypeArchive archive = node.getArchive();
DataTypeManager dataTypeManager = archive.getDataTypeManager();
if (!editorManager.checkEditors(dataTypeManager, true)) {
return;
}
// check for changes and save if desired by user. If cancelled, get out.
if (!ArchiveUtils.canClose(archive, gTree)) {
if (!plugin.resolveModifiedArchive(archive)) {
return;
}
editorManager.dismissEditors(dataTypeManager);
releaseLock(archive);
}
gTree.restoreTreeState(treeState);
}
private boolean canReleaseLock(FileArchive archive, Component parent) {
if (archive.getFile() == null) {
Msg.showInfo(getClass(), parent,
"Unsaved Archive", "Unsaved Archives must first be saved.");
return false;
}
return true;
}
private void releaseLock(FileArchive archive) {
try {
archive.releaseWriteLock();
}
catch (IOException ioe) {
Msg.showError(this, plugin.getProvider().getComponent(),
"Unable to Release Write Lock",
"Problem attempting to release write lock for archive: " + archive.getName() +
"\nMessage: " + ioe.getMessage(), ioe);
ArchiveManager archiveManager = plugin.getArchiveManager();
archiveManager.reopenFileArchive(archive, false);
}
}
}

Some files were not shown because too many files have changed in this diff Show More