GP-89 - Scripting - added JSON support via GSON

Closes #1982
This commit is contained in:
dragonmacher 2020-09-17 14:17:24 -04:00
parent 3ffbf09e52
commit 8216440278
11 changed files with 311 additions and 526 deletions

View file

@ -13,56 +13,51 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
// List function names and entry point addresses to a file // List function names and entry point addresses to a file in JSON format
//@category Functions //@category Functions
import ghidra.app.plugin.core.script.Ingredient; import java.io.File;
import ghidra.app.plugin.core.script.IngredientDescription; import java.io.FileWriter;
import ghidra.app.script.GatherParamPanel;
import com.google.gson.*;
import com.google.gson.stream.JsonWriter;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import java.io.*; public class ExportFunctionInfoScript extends GhidraScript {
public class ExportFunctionInfoScript extends GhidraScript implements Ingredient { private static final String NAME = "name";
private static final String ENTRY = "entry";
@Override @Override
public void run() throws Exception { public void run() throws Exception {
IngredientDescription[] ingredients = getIngredientDescriptions();
for (int i = 0; i < ingredients.length; i++) { Gson gson = new GsonBuilder().setPrettyPrinting().create();
state.addParameter(ingredients[i].getID(), ingredients[i].getLabel(),
ingredients[i].getType(), ingredients[i].getDefaultValue()); File outputFile = askFile("Please Select Output File", "Choose");
} JsonWriter jsonWriter = new JsonWriter(new FileWriter(outputFile));
if (!state.displayParameterGatherer("Script Options")) { jsonWriter.beginArray();
return;
}
File outputNameFile = (File) state.getEnvironmentVar("FunctionNameOutputFile");
PrintWriter pWriter = new PrintWriter(new FileOutputStream(outputNameFile));
Listing listing = currentProgram.getListing(); Listing listing = currentProgram.getListing();
FunctionIterator iter = listing.getFunctions(true); FunctionIterator iter = listing.getFunctions(true);
while (iter.hasNext() && !monitor.isCancelled()) { while (iter.hasNext() && !monitor.isCancelled()) {
Function f = iter.next(); Function f = iter.next();
String fName = f.getName();
String name = f.getName();
Address entry = f.getEntryPoint(); Address entry = f.getEntryPoint();
if (entry == null) {
pWriter.println("/* FUNCTION_NAME_ " + fName + " FUNCTION_ADDR_ " + JsonObject json = new JsonObject();
"NO_ENTRY_POINT" + " */"); json.addProperty(NAME, name);
println("WARNING: no entry point for " + fName); json.addProperty(ENTRY, entry.toString());
}
else { gson.toJson(json, jsonWriter);
pWriter.println("/* FUNCTION_NAME_ " + fName + " FUNCTION_ADDR_ " + entry + " */");
}
} }
pWriter.close();
}
@Override jsonWriter.endArray();
public IngredientDescription[] getIngredientDescriptions() { jsonWriter.close();
IngredientDescription[] retVal =
new IngredientDescription[] { new IngredientDescription("FunctionNameOutputFile",
"Output Function Name File", GatherParamPanel.FILE, "") };
return retVal;
}
println("Wrote functions to " + outputFile);
}
} }

View file

@ -1,153 +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.
*/
//searches for pre-defined patterns and free space in code images
import java.util.List;
import java.util.stream.Collectors;
import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.plugin.core.script.Ingredient;
import ghidra.app.plugin.core.script.IngredientDescription;
import ghidra.app.plugin.core.searchmem.RegExSearchData;
import ghidra.app.script.GatherParamPanel;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.search.memory.*;
public class FindEmptySpaceScript extends GhidraScript implements Ingredient {
@Override
public void run() throws Exception {
IngredientDescription[] ingredients = getIngredientDescriptions();
for (IngredientDescription ingredient : ingredients) {
state.addParameter(ingredient.getID(), ingredient.getLabel(), ingredient.getType(),
ingredient.getDefaultValue());
}
if (!state.displayParameterGatherer("Empty Area Finder Options")) {
return;
}
String emptyArea = (String) state.getEnvironmentVar("EmptyAreaData");
Integer threshold = (Integer) state.getEnvironmentVar("Threshold");
Integer align = (Integer) state.getEnvironmentVar("Alignment");
String stem = (String) state.getEnvironmentVar("NameStem");
findEmptyAreas(emptyArea, threshold, align, stem);
}
protected void findEmptyAreas(String emptyArea, Integer threshold, Integer align, String stem)
throws Exception {
String emptyAreaPlusThreshold = emptyArea + "{" + threshold + ",}";
if (align < currentProgram.getLanguage().getInstructionAlignment()) {
align = currentProgram.getLanguage().getInstructionAlignment();
println(
" Adjusting alignment to minimum instruction alignment for this processor; new alignment is " +
align + " bytes");
}
else if ((align % currentProgram.getLanguage().getInstructionAlignment()) != 0) {
align = align + (align % currentProgram.getLanguage().getInstructionAlignment());
println(
" Adjusting alignment to match processor instruction alignment; new alignment is " +
align + " bytes");
}
println(" Searching initialized memory for " + emptyAreaPlusThreshold +
"; minimum size = " + threshold + " bytes ; alignment = " + align +
" bytes; search limited to first 1000 matches");
AddressSetView addrs = currentProgram.getMemory().getLoadedAndInitializedAddressSet();
SearchInfo searchInfo = new SearchInfo(new RegExSearchData(emptyAreaPlusThreshold), 1000,
false, true, align, true, null);
RegExMemSearcherAlgorithm searcher =
new RegExMemSearcherAlgorithm(searchInfo, addrs, currentProgram, true);
ListAccumulator<MemSearchResult> accumulator = new ListAccumulator<>();
searcher.search(accumulator, monitor);
List<MemSearchResult> results = accumulator.asList();
List<Address> addresses =
results.stream().map(r -> r.getAddress()).collect(Collectors.toList());
int numMatches = 0;
long maxLen = 0;
if (results.isEmpty()) {
println(" FAILURE: Could not find any empty areas with regexp = " +
emptyAreaPlusThreshold + "and alignment = " + align + " bytes");
return;
}
//put matches into an address set, thereby coalescing ranges
AddressSet addrSet = new AddressSet();
for (MemSearchResult result : results) {
Address match = result.getAddress();
int len = result.getLength();
addrSet.addRange(match, match.addNoWrap(len));
}
//iterate over the set items that matched
for (AddressRange range : addrSet) {
long len = range.getLength();
addLabelAndExportSym(range.getMinAddress(), len, stem, "emptyArea", "size = " + len +
" bytes (alignment = " + align + " bytes; min size = " + threshold + " bytes)");
numMatches++;
if (len > maxLen) {
maxLen = len;
}
}
println(" Found " + numMatches +
" empty areas meeting size and alignment requirements; maximum length found = " +
maxLen + " bytes");
}
protected void addLabelAndExportSym(Address matchAddr, long len, String stem, String tag,
String optComment) {
String label = stem + "_" + matchAddr + "_" + len;
label = label.replaceAll(":", "_");
String comment = "{@exportsym " + tag + " " + optComment + "}";
CodeUnit cd = currentProgram.getListing().getCodeUnitAt(matchAddr);
if (cd == null) {
return;
}
AddLabelCmd lcmd = new AddLabelCmd(matchAddr, label, false, SourceType.USER_DEFINED);
lcmd.applyTo(currentProgram);
String commentThere = cd.getComment(CodeUnit.EOL_COMMENT);
if (commentThere != null) {
comment = commentThere + "\n" + comment;
}
cd.setComment(CodeUnit.EOL_COMMENT, comment);
}
@Override
public IngredientDescription[] getIngredientDescriptions() {
IngredientDescription[] retVal = new IngredientDescription[] {
new IngredientDescription("EmptyAreaData", "Regular Expression Data Pattern",
GatherParamPanel.STRING, "\\xff"),
new IngredientDescription("Threshold", "Minimum Size (decimal bytes)",
GatherParamPanel.INTEGER, ""),
new IngredientDescription("Alignment", "Alignment (decimal bytes)",
GatherParamPanel.INTEGER, ""),
new IngredientDescription("NameStem", "Optional Label Stem", GatherParamPanel.STRING,
"EMPTY") };
return retVal;
}
}

View file

@ -1,97 +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.
*/
//Places header structure on overlay segments
import java.io.File;
import ghidra.app.plugin.core.script.Ingredient;
import ghidra.app.plugin.core.script.IngredientDescription;
import ghidra.app.script.GatherParamPanel;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.util.CodeUnitInsertionException;
public class OverlayHeadersScript extends GhidraScript implements Ingredient {
@Override
public void run() throws Exception {
// Get our configuration info and save for other scripts to use
IngredientDescription[] ingredients = getIngredientDescriptions();
for (IngredientDescription ingredient : ingredients) {
state.addParameter(ingredient.getID(), ingredient.getLabel(), ingredient.getType(),
ingredient.getDefaultValue());
}
if (!state.displayParameterGatherer("Script Options")) {
return;
}
// Get our parameters for use here
String overlayName = (String) state.getEnvironmentVar("OverlayName");
File dataTypeArchive = (File) state.getEnvironmentVar("OverlayHeaderArchive");
String dataTypeName = (String) state.getEnvironmentVar("OverlayHeaderName"); // must include datatype category
// Create our history logger
Address histAddr = currentProgram.getMemory().getMinAddress();
String tmpString = "\nScript: OverlayHeaders()\n";
tmpString = tmpString + " Add " + dataTypeName + " structure\n from " +
dataTypeArchive.toString();
// Get the datatype that we want to place on the overlays
FileDataTypeManager dataTypeFileManager = openDataTypeArchive(dataTypeArchive, true);
DataType dataType = dataTypeFileManager.getDataType(dataTypeName);
dataTypeFileManager.close();
if (dataType == null) {
println("Can't find data type " + dataTypeName + " in " + dataTypeArchive.toString());
throw new Exception(
"Can't find data type " + dataTypeName + "\n in " + dataTypeArchive.toString());
}
// Now iterate over overlays the lay down structure
AddressSetView searchSet = currentProgram.getMemory();
AddressRangeIterator addressRanges = searchSet.getAddressRanges(true);
monitor.initialize(searchSet.getNumAddresses());
int progressCount = 0;
while (addressRanges.hasNext() && !monitor.isCancelled()) {
AddressRange range = addressRanges.next();
Address startAddr = range.getMinAddress();
String rangeName = startAddr.toString();
if (rangeName.startsWith(overlayName)) {
try {
createData(startAddr, dataType);
}
catch (CodeUnitInsertionException ex) {
println("Error creating data type: " + ex);
}
}
progressCount += range.getLength();
monitor.setProgress(progressCount);
}
}
@Override
public IngredientDescription[] getIngredientDescriptions() {
IngredientDescription[] retVal = new IngredientDescription[] {
new IngredientDescription("OverlayName", "Overlay Name", GatherParamPanel.STRING, "ov"),
new IngredientDescription("OverlayHeaderArchive", "Overlay Header Archive",
GatherParamPanel.FILE, ""),
new IngredientDescription("OverlayHeaderName", "Overlay Header Name",
GatherParamPanel.STRING, "/overlay_header") };
return retVal;
}
}

View file

@ -1,20 +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.script;
public interface Ingredient {
IngredientDescription [] getIngredientDescriptions();
}

View file

@ -1,54 +0,0 @@
/* ###
* 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.script;
public class IngredientDescription {
private boolean visited;
private String id;
private String label;
private int type;
private Object defaultValue;
public IngredientDescription(String id, String label, int type, Object defaultValue) {
this.id = id;
this.label = label;
this.type = type;
this.defaultValue = defaultValue;
visited = false;
}
public boolean wasVisited() {
return visited;
}
public String getLabel() {
return label;
}
public String getID() {
return id;
}
public int getType() {
return type;
}
public Object getDefaultValue() {
return defaultValue;
}
}

View file

@ -0,0 +1,156 @@
/* ###
* 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.script;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
import docking.widgets.filechooser.GhidraFileChooser;
import generic.json.Json;
import ghidra.framework.Application;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.test.*;
/**
* Tests the {@code ExportFunctionInfoScript}, which writes Ghidra function object info in JSON
* form for the entire program
*/
public class ExportFunctionInfoScriptTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private File script;
private Program program;
private Function f1;
private Function f2;
@Before
public void setUp() throws Exception {
program = buildProgram();
env = new TestEnv();
env.launchDefaultTool(program);
String scriptPath = "ghidra_scripts/ExportFunctionInfoScript.java";
script = Application.getModuleFile("Base", scriptPath).getFile(true);
}
private Program buildProgram() throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder("Test", true, this);
builder.createMemory(".text", "0x1001000", 0x40);
f1 = builder.createFunction("0x1001000");
f2 = builder.createFunction("0x1001020");
return builder.getProgram();
}
@Test
public void testScript() throws Exception {
File outputFile = createTempFileForTest();
ScriptTaskListener listener = env.runScript(script);
chooseFile(outputFile);
waitForScriptCompletion(listener, 20000);
assertFunctionsInFile(outputFile, f1, f2);
}
private void assertFunctionsInFile(File file, Function... functions)
throws Exception {
List<Function> testFunctions = new ArrayList<>(List.of(f1, f2));
List<TestJsonFunction> jsons = readFromJson(file);
jsons.forEach(jsonFunction -> assertFunction(jsonFunction, testFunctions));
assertThat("Not all program functions written to json file",
testFunctions, is(empty()));
}
private List<TestJsonFunction> readFromJson(File file) throws Exception {
List<TestJsonFunction> results = new ArrayList<>();
Gson gson = new Gson();
BufferedReader br = new BufferedReader(new FileReader(file));
JsonReader reader = new JsonReader(br);
// the file is an array of objects
reader.beginArray();
while (reader.hasNext()) {
TestJsonFunction function = gson.fromJson(reader, TestJsonFunction.class);
results.add(function);
}
reader.endArray();
reader.close();
return results;
}
private void assertFunction(TestJsonFunction function, List<Function> testFunctions) {
Function match = null;
for (Function expected : testFunctions) {
if (function.matches(expected)) {
match = expected;
break;
}
}
assertNotNull("Unexpected function written to file", match);
testFunctions.remove(match);
}
private void chooseFile(File file) throws Exception {
GhidraFileChooser chooser = waitForDialogComponent(GhidraFileChooser.class);
runSwing(() -> chooser.setSelectedFile(file));
waitForUpdateOnChooser(chooser);
pressButtonByText(chooser.getComponent(), "Choose");
waitForSwing();
}
private class TestJsonFunction {
private String name;
private String entry;
boolean matches(Function expected) {
return name.equals(expected.getName()) &&
entry.equals(expected.getEntryPoint().toString());
}
@Override
public String toString() {
// this is only for debug; not required
return Json.toString(this);
}
}
}

View file

@ -1,57 +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.
*/
//Decompile an entire program
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.plugin.core.script.Ingredient;
import ghidra.app.plugin.core.script.IngredientDescription;
import ghidra.app.script.GatherParamPanel;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.Option;
import ghidra.app.util.exporter.CppExporter;
public class Decompile extends GhidraScript implements Ingredient {
@Override
public void run() throws Exception {
IngredientDescription[] ingredients = getIngredientDescriptions();
for (IngredientDescription ingredient : ingredients) {
state.addParameter(ingredient.getID(), ingredient.getLabel(), ingredient.getType(),
ingredient.getDefaultValue());
}
if (!state.displayParameterGatherer("Script Options")) {
return;
}
File outputFile = (File) state.getEnvironmentVar("COutputFile");
CppExporter cppExporter = new CppExporter();
List<Option> options = new ArrayList<Option>();
options.add(new Option(CppExporter.CREATE_HEADER_FILE, new Boolean(false)));
cppExporter.setOptions(options);
cppExporter.setExporterServiceProvider(state.getTool());
cppExporter.export(outputFile, currentProgram, null, monitor);
}
@Override
public IngredientDescription[] getIngredientDescriptions() {
IngredientDescription[] retVal = new IngredientDescription[] {
new IngredientDescription("COutputFile", "Output C File", GatherParamPanel.FILE, "") };
return retVal;
}
}

View file

@ -18,8 +18,8 @@ dependencies {
compile "org.apache.commons:commons-collections4:4.1" compile "org.apache.commons:commons-collections4:4.1"
compile "org.apache.commons:commons-lang3:3.9" compile "org.apache.commons:commons-lang3:3.9"
compile "org.apache.commons:commons-text:1.6" compile "org.apache.commons:commons-text:1.6"
compile "commons-io:commons-io:2.6"
compile "commons-io:commons-io:2.6" compile "com.google.code.gson:gson:2.8.6"
compileOnly "junit:junit:4.12" compileOnly "junit:junit:4.12"
} }

View file

@ -67,7 +67,6 @@ public class ExtensionUtils {
public static String PROPERTIES_FILE_NAME = "extension.properties"; public static String PROPERTIES_FILE_NAME = "extension.properties";
public static String PROPERTIES_FILE_NAME_UNINSTALLED = "extension.properties.uninstalled"; public static String PROPERTIES_FILE_NAME_UNINSTALLED = "extension.properties.uninstalled";
/** /**
* Returns a set of all extensions known to Ghidra, represented by * Returns a set of all extensions known to Ghidra, represented by
* {@link ExtensionDetails} objects. This will include all installed * {@link ExtensionDetails} objects. This will include all installed
@ -76,7 +75,7 @@ public class ExtensionUtils {
* Note that this method will only look in the known extension folder locations: * Note that this method will only look in the known extension folder locations:
* <ul> * <ul>
* <li>{@link ApplicationLayout#getExtensionArchiveDir}</li> * <li>{@link ApplicationLayout#getExtensionArchiveDir}</li>
* <li>{@link ApplicationLayout#getExtensionInstallationDir}</li> * <li>{@link ApplicationLayout#getExtensionInstallationDirs}</li>
* </ul> * </ul>
* If users install extensions from other locations, the installed version of * If users install extensions from other locations, the installed version of
* the extension will be known, but the source archive location will not be retained. * the extension will be known, but the source archive location will not be retained.
@ -123,13 +122,14 @@ public class ExtensionUtils {
/** /**
* Returns all installed extensions. These are all the extensions found in * Returns all installed extensions. These are all the extensions found in
* {@link ApplicationLayout#getExtensionInstallationDir}. * {@link ApplicationLayout#getExtensionInstallationDirs}.
* *
* @param includeUninstalled if true, include extensions that have been marked for removal * @param includeUninstalled if true, include extensions that have been marked for removal
* @return set of installed extensions * @return set of installed extensions
* @throws ExtensionException if the extension details cannot be retrieved * @throws ExtensionException if the extension details cannot be retrieved
*/ */
public static Set<ExtensionDetails> getInstalledExtensions(boolean includeUninstalled) throws ExtensionException { public static Set<ExtensionDetails> getInstalledExtensions(boolean includeUninstalled)
throws ExtensionException {
ApplicationLayout layout = Application.getApplicationLayout(); ApplicationLayout layout = Application.getApplicationLayout();
@ -165,7 +165,7 @@ public class ExtensionUtils {
return extensions; return extensions;
} }
/** /**
* Returns all archived extensions. These are all the extensions found in * Returns all archived extensions. These are all the extensions found in
* {@link ApplicationLayout#getExtensionArchiveDir}. * {@link ApplicationLayout#getExtensionArchiveDir}.
@ -215,7 +215,6 @@ public class ExtensionUtils {
} }
} }
} }
return extensions; return extensions;
} }
@ -262,7 +261,7 @@ public class ExtensionUtils {
e); e);
return false; return false;
} }
return runInstallTask(rFile.getFile(false)); return runInstallTask(rFile.getFile(false));
} }
@ -306,7 +305,7 @@ public class ExtensionUtils {
} }
ResourceFile file = new ResourceFile(extension.getArchivePath()); ResourceFile file = new ResourceFile(extension.getArchivePath());
// We need to handle a special case: If the user selects an extension to uninstall using // We need to handle a special case: If the user selects an extension to uninstall using
// the GUI then tries to reinstall it without restarting Ghidra, the extension hasn't actually // the GUI then tries to reinstall it without restarting Ghidra, the extension hasn't actually
// been removed yet; just the manifest file has been renamed. In this case we don't need to go through // been removed yet; just the manifest file has been renamed. In this case we don't need to go through
@ -315,7 +314,7 @@ public class ExtensionUtils {
if (installDir.exists()) { if (installDir.exists()) {
return restoreStateFiles(installDir); return restoreStateFiles(installDir);
} }
if (install(file)) { if (install(file)) {
extension.setInstallPath(installDir + File.separator + extension.getName()); extension.setInstallPath(installDir + File.separator + extension.getName());
return true; return true;
@ -421,7 +420,7 @@ public class ExtensionUtils {
return false; return false;
} }
/** /**
* Returns true if the given file is a valid .zip archive. * Returns true if the given file is a valid .zip archive.
* *
@ -453,7 +452,7 @@ public class ExtensionUtils {
throw new ExtensionException(e.getMessage(), ExtensionExceptionType.ZIP_ERROR); throw new ExtensionException(e.getMessage(), ExtensionExceptionType.ZIP_ERROR);
} }
} }
/** /**
* Returns a list of files representing all the <code>extension.properties</code> files found * Returns a list of files representing all the <code>extension.properties</code> files found
* under a given directory. This will ONLY search the given directory and its immediate children. * under a given directory. This will ONLY search the given directory and its immediate children.
@ -517,8 +516,10 @@ public class ExtensionUtils {
List<ResourceFile> tempFiles = Arrays.asList(rfiles); List<ResourceFile> tempFiles = Arrays.asList(rfiles);
Optional<ResourceFile> file = Optional<ResourceFile> file =
tempFiles.stream().filter(f -> f.getName().equals(PROPERTIES_FILE_NAME) || tempFiles.stream()
f.getName().equals(PROPERTIES_FILE_NAME_UNINSTALLED)).findFirst(); .filter(f -> f.getName().equals(PROPERTIES_FILE_NAME) ||
f.getName().equals(PROPERTIES_FILE_NAME_UNINSTALLED))
.findFirst();
if (file.isPresent()) { if (file.isPresent()) {
return file.get(); return file.get();
} }
@ -547,14 +548,14 @@ public class ExtensionUtils {
} }
else { else {
copyToInstallationFolder(file, monitor); copyToInstallationFolder(file, monitor);
} }
installed.set(true); installed.set(true);
} }
catch (ExtensionException e) { catch (ExtensionException e) {
// If there's a problem copying files, check to see if there's already an extension // If there's a problem copying files, check to see if there's already an extension
// with this name in the install location that was slated for removal. If so, just // with this name in the install location that was slated for removal. If so, just
// restore the extension properties and manifest files. // restore the extension properties and manifest files.
if (e.getExceptionType() == ExtensionExceptionType.COPY_ERROR || if (e.getExceptionType() == ExtensionExceptionType.COPY_ERROR ||
e.getExceptionType() == ExtensionExceptionType.DUPLICATE_FILE_ERROR) { e.getExceptionType() == ExtensionExceptionType.DUPLICATE_FILE_ERROR) {
File errorFile = e.getErrorFile(); File errorFile = e.getErrorFile();
@ -563,14 +564,15 @@ public class ExtensionUtils {
ResourceFile installDir = Application.getApplicationLayout() ResourceFile installDir = Application.getApplicationLayout()
.getExtensionInstallationDirs() .getExtensionInstallationDirs()
.get(0); .get(0);
// Get the root directory of the extension (strip off the install folder location and // Get the root directory of the extension (strip off the install folder location and
// grab the first part of the remaining path). // grab the first part of the remaining path).
// //
// eg: If errorFile is "/Users/johnG/Ghidra/Extensions/MyExtensionName/subdir1/problemFile" // eg: If errorFile is "/Users/johnG/Ghidra/Extensions/MyExtensionName/subdir1/problemFile"
// And installDir is "/Users/johnG/Ghidra/Extensions" // And installDir is "/Users/johnG/Ghidra/Extensions"
// We need to get "MyExtensionName" // We need to get "MyExtensionName"
String extPath = errorFile.getAbsolutePath().substring(installDir.getAbsolutePath().length()+1); String extPath = errorFile.getAbsolutePath()
.substring(installDir.getAbsolutePath().length() + 1);
int slashIndex = extPath.indexOf(File.separator); int slashIndex = extPath.indexOf(File.separator);
String extName; String extName;
if (slashIndex == -1) { if (slashIndex == -1) {
@ -580,12 +582,13 @@ public class ExtensionUtils {
extName = extPath.substring(0, extPath.indexOf(File.separator)); extName = extPath.substring(0, extPath.indexOf(File.separator));
} }
boolean success = restoreStateFiles(new File(installDir.getAbsolutePath() + File.separator + extName)); boolean success = restoreStateFiles(
new File(installDir.getAbsolutePath() + File.separator + extName));
installed.set(success); installed.set(success);
} }
} }
if (installed.get() == false) { if (installed.get() == false) {
Msg.showError(null, null, "Installation Error", "Error installing extension [" + Msg.showError(null, null, "Installation Error", "Error installing extension [" +
file.getName() + "]." + " " + e.getExceptionType()); file.getName() + "]." + " " + e.getExceptionType());
@ -601,7 +604,7 @@ public class ExtensionUtils {
return installed.get(); return installed.get();
} }
/** /**
* Recursively searches a given directory for any module manifest and extension * Recursively searches a given directory for any module manifest and extension
* properties files that are in an installed state and converts them to an uninstalled * properties files that are in an installed state and converts them to an uninstalled
@ -617,7 +620,7 @@ public class ExtensionUtils {
* @return false if any renames fail * @return false if any renames fail
*/ */
public static boolean removeStateFiles(ExtensionDetails extension) { public static boolean removeStateFiles(ExtensionDetails extension) {
// Sanity check // Sanity check
if (extension == null || extension.getInstallPath() == null || if (extension == null || extension.getInstallPath() == null ||
extension.getInstallPath().isEmpty()) { extension.getInstallPath().isEmpty()) {
@ -625,23 +628,29 @@ public class ExtensionUtils {
} }
boolean success = true; boolean success = true;
List<File> manifestFiles = new ArrayList<>(); List<File> manifestFiles = new ArrayList<>();
ExtensionUtils.findFilesWithName(new File(extension.getInstallPath()), ModuleUtilities.MANIFEST_FILE_NAME, manifestFiles); ExtensionUtils.findFilesWithName(new File(extension.getInstallPath()),
ModuleUtilities.MANIFEST_FILE_NAME, manifestFiles);
for (File f : manifestFiles) { for (File f : manifestFiles) {
if (f.exists()) { if (f.exists()) {
File newFile = new File(f.getAbsolutePath().replace(ModuleUtilities.MANIFEST_FILE_NAME, ModuleUtilities.MANIFEST_FILE_NAME_UNINSTALLED) ); File newFile = new File(f.getAbsolutePath()
.replace(ModuleUtilities.MANIFEST_FILE_NAME,
ModuleUtilities.MANIFEST_FILE_NAME_UNINSTALLED));
if (!f.renameTo(newFile)) { if (!f.renameTo(newFile)) {
success = false; success = false;
} }
} }
} }
List<File> propFiles = new ArrayList<>(); List<File> propFiles = new ArrayList<>();
ExtensionUtils.findFilesWithName(new File(extension.getInstallPath()), ExtensionUtils.PROPERTIES_FILE_NAME, propFiles); ExtensionUtils.findFilesWithName(new File(extension.getInstallPath()),
ExtensionUtils.PROPERTIES_FILE_NAME, propFiles);
for (File f : propFiles) { for (File f : propFiles) {
if (f.exists()) { if (f.exists()) {
File newFile = new File(f.getAbsolutePath().replace(ExtensionUtils.PROPERTIES_FILE_NAME, ExtensionUtils.PROPERTIES_FILE_NAME_UNINSTALLED) ); File newFile = new File(f.getAbsolutePath()
.replace(ExtensionUtils.PROPERTIES_FILE_NAME,
ExtensionUtils.PROPERTIES_FILE_NAME_UNINSTALLED));
if (!f.renameTo(newFile)) { if (!f.renameTo(newFile)) {
success = false; success = false;
} }
@ -665,34 +674,37 @@ public class ExtensionUtils {
* @return false if any renames fail * @return false if any renames fail
*/ */
public static boolean restoreStateFiles(File rootDir) { public static boolean restoreStateFiles(File rootDir) {
boolean success = true; boolean success = true;
List<File> manifestFiles = new ArrayList<>(); List<File> manifestFiles = new ArrayList<>();
findFilesWithName(rootDir, ModuleUtilities.MANIFEST_FILE_NAME_UNINSTALLED, manifestFiles); findFilesWithName(rootDir, ModuleUtilities.MANIFEST_FILE_NAME_UNINSTALLED, manifestFiles);
for (File f : manifestFiles) { for (File f : manifestFiles) {
if (f.exists()) { if (f.exists()) {
File newFile = new File(f.getAbsolutePath().replace(ModuleUtilities.MANIFEST_FILE_NAME_UNINSTALLED, ModuleUtilities.MANIFEST_FILE_NAME) ); File newFile = new File(f.getAbsolutePath()
.replace(ModuleUtilities.MANIFEST_FILE_NAME_UNINSTALLED,
ModuleUtilities.MANIFEST_FILE_NAME));
if (!f.renameTo(newFile)) { if (!f.renameTo(newFile)) {
success = false; success = false;
} }
} }
} }
List<File> propFiles = new ArrayList<>(); List<File> propFiles = new ArrayList<>();
findFilesWithName(rootDir, PROPERTIES_FILE_NAME_UNINSTALLED, propFiles); findFilesWithName(rootDir, PROPERTIES_FILE_NAME_UNINSTALLED, propFiles);
for (File f : propFiles) { for (File f : propFiles) {
if (f.exists()) { if (f.exists()) {
File newFile = new File(f.getAbsolutePath().replace(PROPERTIES_FILE_NAME_UNINSTALLED, PROPERTIES_FILE_NAME) ); File newFile = new File(f.getAbsolutePath()
.replace(PROPERTIES_FILE_NAME_UNINSTALLED, PROPERTIES_FILE_NAME));
if (!f.renameTo(newFile)) { if (!f.renameTo(newFile)) {
success = false; success = false;
} }
} }
} }
return success; return success;
} }
/** /**
* *
* @param root the starting directory to search recursively * @param root the starting directory to search recursively
@ -700,22 +712,22 @@ public class ExtensionUtils {
* @param foundFiles list of all matching files * @param foundFiles list of all matching files
*/ */
public static void findFilesWithName(File root, String fileName, List<File> foundFiles) { public static void findFilesWithName(File root, String fileName, List<File> foundFiles) {
if (root == null || foundFiles == null) { if (root == null || foundFiles == null) {
return; return;
}
if (root.isDirectory()) {
File[] files = root.listFiles();
if (files != null) {
for (File file : files) {
findFilesWithName(file, fileName, foundFiles);
}
}
}
else if (root.isFile() && root.getName().equals(fileName)) {
foundFiles.add(root);
} }
if (root.isDirectory()) {
File[] files = root.listFiles();
if (files != null) {
for (File file : files) {
findFilesWithName(file, fileName, foundFiles);
}
}
}
else if (root.isFile() && root.getName().equals(fileName)) {
foundFiles.add(root);
}
} }
/** /**
@ -761,7 +773,7 @@ public class ExtensionUtils {
throws ExtensionException, CancelledException { throws ExtensionException, CancelledException {
File newDir = null; File newDir = null;
try { try {
newDir = newDir =
new File(Application.getApplicationLayout().getExtensionInstallationDirs().get(0) + new File(Application.getApplicationLayout().getExtensionInstallationDirs().get(0) +
File.separator + extension.getName()); File.separator + extension.getName());
@ -774,11 +786,11 @@ public class ExtensionUtils {
} }
/** /**
* Unpacks a given zip file to {@link ApplicationLayout#getExtensionInstallationDir}. The * Unpacks a given zip file to {@link ApplicationLayout#getExtensionInstallationDirs}. The
* file permissions in the original zip will be retained. * file permissions in the original zip will be retained.
* <p> * <p>
* Note: This method uses the Apache zip files since they keep track of permissions info; * Note: This method uses the Apache zip files since they keep track of permissions info;
* the built-in java objects (ZipEntry et al.) do not. * the built-in java objects (e.g., ZipEntry) do not.
* *
* @param zipFile the zip file to unpack * @param zipFile the zip file to unpack
* @param monitor the task monitor * @param monitor the task monitor
@ -993,7 +1005,7 @@ public class ExtensionUtils {
String author = props.getProperty("author"); String author = props.getProperty("author");
String date = props.getProperty("createdOn"); String date = props.getProperty("createdOn");
String version = props.getProperty("version"); String version = props.getProperty("version");
return new ExtensionDetails(name, desc, author, date, version); return new ExtensionDetails(name, desc, author, date, version);
} }

View file

@ -4,9 +4,7 @@ eclipse.project.name = '_JsonDoclet'
apply plugin: 'java' apply plugin: 'java'
dependencies { dependencies {
compile('com.googlecode.json-simple:json-simple:1.1.1') { compile "com.google.code.gson:gson:2.8.6"
exclude group: 'junit', module: 'junit'
}
} }
rootProject.createJsondocs.dependsOn jar rootProject.createJsondocs.dependsOn jar

View file

@ -24,9 +24,7 @@ import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter; import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
import org.json.simple.JSONArray; import com.google.gson.*;
import org.json.simple.JSONObject;
import com.sun.source.doctree.*; import com.sun.source.doctree.*;
import com.sun.source.util.DocTrees; import com.sun.source.util.DocTrees;
@ -35,8 +33,9 @@ import jdk.javadoc.doclet.*;
/** /**
* Doclet that outputs javadoc in JSON format (instead of HTML). Things like Python can then * Doclet that outputs javadoc in JSON format (instead of HTML). Things like Python can then
* read in the JSON and easily access all of the javadoc elements. * read in the JSON and easily access all of the javadoc elements.
*
* To run: gradle zipJavadocs
*/ */
@SuppressWarnings("unchecked")
public class JsonDoclet implements Doclet { public class JsonDoclet implements Doclet {
private Reporter log; private Reporter log;
@ -45,6 +44,11 @@ public class JsonDoclet implements Doclet {
private DocletEnvironment docEnv; private DocletEnvironment docEnv;
private DocTrees docTrees; private DocTrees docTrees;
private Gson gson = new GsonBuilder()
.setPrettyPrinting()
.serializeNulls()
.create();
@Override @Override
public void init(Locale locale, Reporter reporter) { public void init(Locale locale, Reporter reporter) {
this.log = reporter; this.log = reporter;
@ -128,13 +132,13 @@ public class JsonDoclet implements Doclet {
} }
/** /**
* Converts a class {@link TypeElement} to a {@link JSONObject}. * Converts a class {@link TypeElement} to a {@link JsonObject}.
* *
* @param classElement the class {@link TypeElement} to convert * @param classElement the class {@link TypeElement} to convert
* @return A json object that represents the class. * @return A json object that represents the class.
*/ */
private JSONObject classToJson(TypeElement classElement) { private JsonObject classToJson(TypeElement classElement) {
JSONObject classObj = new JSONObject(); JsonObject classObj = new JsonObject();
processClassAttributes(classElement, classObj); processClassAttributes(classElement, classObj);
processFieldAndMethodAttributes(classElement, classObj); processFieldAndMethodAttributes(classElement, classObj);
return classObj; return classObj;
@ -146,11 +150,11 @@ public class JsonDoclet implements Doclet {
* @param classElement the class element to parse * @param classElement the class element to parse
* @param classObj the json object to populate * @param classObj the json object to populate
*/ */
private void processClassAttributes(TypeElement classElement, JSONObject classObj) { private void processClassAttributes(TypeElement classElement, JsonObject classObj) {
classObj.put("name", classElement.getSimpleName().toString()); classObj.addProperty("name", classElement.getSimpleName().toString());
classObj.put("comment", getComment(docTrees.getDocCommentTree(classElement))); classObj.addProperty("comment", getComment(docTrees.getDocCommentTree(classElement)));
classObj.put("javadoc", getJavadoc(docTrees.getDocCommentTree(classElement))); classObj.addProperty("javadoc", getJavadoc(docTrees.getDocCommentTree(classElement)));
classObj.put("static", classElement.getModifiers().contains(Modifier.STATIC)); classObj.addProperty("static", classElement.getModifiers().contains(Modifier.STATIC));
addInterfaces(classElement, classObj); addInterfaces(classElement, classObj);
addSuperClass(classElement, classObj); addSuperClass(classElement, classObj);
} }
@ -162,8 +166,8 @@ public class JsonDoclet implements Doclet {
* @param typeElement the {@link TypeElement} to parse * @param typeElement the {@link TypeElement} to parse
* @param obj the json object to populate * @param obj the json object to populate
*/ */
private void addInterfaces(TypeElement typeElement, JSONObject obj) { private void addInterfaces(TypeElement typeElement, JsonObject obj) {
JSONArray interfaceArray = new JSONArray(); JsonArray interfaceArray = new JsonArray();
//@formatter:off //@formatter:off
typeElement.getInterfaces() typeElement.getInterfaces()
@ -176,7 +180,7 @@ public class JsonDoclet implements Doclet {
.forEach(ifaceTypeElement -> interfaceArray.add(ifaceTypeElement.getQualifiedName().toString())); .forEach(ifaceTypeElement -> interfaceArray.add(ifaceTypeElement.getQualifiedName().toString()));
//@formatter:on //@formatter:on
obj.put("implements", interfaceArray); obj.add("implements", interfaceArray);
} }
/** /**
@ -186,12 +190,12 @@ public class JsonDoclet implements Doclet {
* @param typeElement the {@link TypeElement} to parse * @param typeElement the {@link TypeElement} to parse
* @param obj the json object to populate * @param obj the json object to populate
*/ */
private void addSuperClass(TypeElement typeElement, JSONObject obj) { private void addSuperClass(TypeElement typeElement, JsonObject obj) {
if (typeElement.getSuperclass() instanceof DeclaredType) { if (typeElement.getSuperclass() instanceof DeclaredType) {
DeclaredType declaredType = (DeclaredType) typeElement.getSuperclass(); DeclaredType declaredType = (DeclaredType) typeElement.getSuperclass();
if (declaredType.asElement() instanceof TypeElement) { if (declaredType.asElement() instanceof TypeElement) {
TypeElement typeEl = (TypeElement) declaredType.asElement(); TypeElement typeEl = (TypeElement) declaredType.asElement();
obj.put("extends", typeEl.getQualifiedName().toString()); obj.addProperty("extends", typeEl.getQualifiedName().toString());
} }
} }
} }
@ -202,29 +206,29 @@ public class JsonDoclet implements Doclet {
* @param classElement the class to parse * @param classElement the class to parse
* @param classObj the json object to populate * @param classObj the json object to populate
*/ */
private void processFieldAndMethodAttributes(TypeElement classElement, JSONObject classObj) { private void processFieldAndMethodAttributes(TypeElement classElement, JsonObject classObj) {
JSONArray fieldArray = new JSONArray(); JsonArray fieldArray = new JsonArray();
JSONArray methodArray = new JSONArray(); JsonArray methodArray = new JsonArray();
for (Element el : classElement.getEnclosedElements()) { for (Element el : classElement.getEnclosedElements()) {
JSONObject obj = new JSONObject(); JsonObject obj = new JsonObject();
obj.put("name", el.getSimpleName().toString()); obj.addProperty("name", el.getSimpleName().toString());
obj.put("comment", getComment(docTrees.getDocCommentTree(el))); obj.addProperty("comment", getComment(docTrees.getDocCommentTree(el)));
obj.put("javadoc", getJavadoc(docTrees.getDocCommentTree(el))); obj.addProperty("javadoc", getJavadoc(docTrees.getDocCommentTree(el)));
obj.put("static", el.getModifiers().contains(Modifier.STATIC)); obj.addProperty("static", el.getModifiers().contains(Modifier.STATIC));
switch (el.getKind()) { switch (el.getKind()) {
case FIELD: case FIELD:
VariableElement varElement = (VariableElement) el; VariableElement varElement = (VariableElement) el;
obj.put("type_long", getTypeLong(el.asType())); obj.addProperty("type_long", getTypeLong(el.asType()));
obj.put("type_short", getTypeShort(el.asType())); obj.addProperty("type_short", getTypeShort(el.asType()));
Object constantValue = varElement.getConstantValue(); Object constantValue = varElement.getConstantValue();
if (constantValue instanceof String) { if (constantValue instanceof String) {
constantValue = "\"" + constantValue + "\""; constantValue = "\"" + constantValue + "\"";
} }
obj.put("constant_value", Objects.toString(constantValue, null)); // only applies to 'final' obj.addProperty("constant_value", Objects.toString(constantValue, null)); // only applies to 'final'
fieldArray.add(obj); fieldArray.add(obj);
break; break;
case CONSTRUCTOR: case CONSTRUCTOR:
@ -255,8 +259,8 @@ public class JsonDoclet implements Doclet {
} }
} }
classObj.put("fields", fieldArray); classObj.add("fields", fieldArray);
classObj.put("methods", methodArray); classObj.add("methods", methodArray);
} }
/** /**
@ -266,14 +270,14 @@ public class JsonDoclet implements Doclet {
* @param execElement the element to parse * @param execElement the element to parse
* @param obj the json object * @param obj the json object
*/ */
private void addParams(ExecutableElement execElement, JSONObject obj) { private void addParams(ExecutableElement execElement, JsonObject obj) {
JSONArray paramsArray = new JSONArray(); JsonArray paramsArray = new JsonArray();
for (VariableElement varElement : execElement.getParameters()) { for (VariableElement varElement : execElement.getParameters()) {
JSONObject paramObj = new JSONObject(); JsonObject paramObj = new JsonObject();
paramObj.put("name", varElement.getSimpleName().toString()); paramObj.addProperty("name", varElement.getSimpleName().toString());
paramObj.put("type_long", getTypeLong(varElement.asType())); paramObj.addProperty("type_long", getTypeLong(varElement.asType()));
paramObj.put("type_short", getTypeShort(varElement.asType())); paramObj.addProperty("type_short", getTypeShort(varElement.asType()));
String comment = ""; String comment = "";
DocCommentTree commentTree = docTrees.getDocCommentTree(execElement); DocCommentTree commentTree = docTrees.getDocCommentTree(execElement);
if (commentTree != null) { if (commentTree != null) {
@ -287,10 +291,10 @@ public class JsonDoclet implements Doclet {
} }
} }
} }
paramObj.put("comment", comment); paramObj.addProperty("comment", comment);
paramsArray.add(paramObj); paramsArray.add(paramObj);
} }
obj.put("params", paramsArray); obj.add("params", paramsArray);
} }
/** /**
@ -300,11 +304,11 @@ public class JsonDoclet implements Doclet {
* @param execElement the element to parse * @param execElement the element to parse
* @param obj the json object * @param obj the json object
*/ */
private void addReturn(ExecutableElement execElement, JSONObject obj) { private void addReturn(ExecutableElement execElement, JsonObject obj) {
TypeMirror returnType = execElement.getReturnType(); TypeMirror returnType = execElement.getReturnType();
JSONObject returnObj = new JSONObject(); JsonObject returnObj = new JsonObject();
returnObj.put("type_long", getTypeLong(returnType)); returnObj.addProperty("type_long", getTypeLong(returnType));
returnObj.put("type_short", getTypeShort(returnType)); returnObj.addProperty("type_short", getTypeShort(returnType));
String comment = ""; String comment = "";
DocCommentTree commentTree = docTrees.getDocCommentTree(execElement); DocCommentTree commentTree = docTrees.getDocCommentTree(execElement);
if (commentTree != null) { if (commentTree != null) {
@ -314,8 +318,8 @@ public class JsonDoclet implements Doclet {
} }
} }
} }
returnObj.put("comment", comment); returnObj.addProperty("comment", comment);
obj.put("return", returnObj); obj.add("return", returnObj);
} }
/** /**
@ -325,14 +329,14 @@ public class JsonDoclet implements Doclet {
* @param execElement the element to parse * @param execElement the element to parse
* @param obj the json object * @param obj the json object
*/ */
private void addExceptions(ExecutableElement execElement, JSONObject obj) { private void addExceptions(ExecutableElement execElement, JsonObject obj) {
JSONArray throwsArray = new JSONArray(); JsonArray throwsArray = new JsonArray();
for (TypeMirror thrownType : execElement.getThrownTypes()) { for (TypeMirror thrownType : execElement.getThrownTypes()) {
JSONObject throwObj = new JSONObject(); JsonObject throwObj = new JsonObject();
String typeLong = getTypeLong(thrownType); String typeLong = getTypeLong(thrownType);
String typeShort = getTypeShort(thrownType); String typeShort = getTypeShort(thrownType);
throwObj.put("type_long", typeLong); throwObj.addProperty("type_long", typeLong);
throwObj.put("type_short", typeShort); throwObj.addProperty("type_short", typeShort);
String comment = ""; String comment = "";
DocCommentTree commentTree = docTrees.getDocCommentTree(execElement); DocCommentTree commentTree = docTrees.getDocCommentTree(execElement);
if (commentTree != null) { if (commentTree != null) {
@ -346,10 +350,10 @@ public class JsonDoclet implements Doclet {
} }
} }
} }
throwObj.put("comment", comment); throwObj.addProperty("comment", comment);
throwsArray.add(throwObj); throwsArray.add(throwObj);
} }
obj.put("throws", throwsArray); obj.add("throws", throwsArray);
} }
/** /**
@ -446,11 +450,12 @@ public class JsonDoclet implements Doclet {
* @param qualifiedName The qualified class name. This name will get converted into a directory * @param qualifiedName The qualified class name. This name will get converted into a directory
* structure. * structure.
*/ */
private void writeJsonToFile(JSONObject json, Name qualifiedName) { private void writeJsonToFile(JsonObject json, Name qualifiedName) {
File jsonFile = new File(destDir, qualifiedName.toString().replace('.', '/') + ".json"); File jsonFile = new File(destDir, qualifiedName.toString().replace('.', '/') + ".json");
jsonFile.getParentFile().mkdirs(); jsonFile.getParentFile().mkdirs();
try (PrintWriter writer = new PrintWriter(new FileWriter(jsonFile))) { try (PrintWriter writer = new PrintWriter(new FileWriter(jsonFile))) {
writer.println(json.toJSONString()); writer.println(gson.toJson(json));
} }
catch (IOException e) { catch (IOException e) {
e.printStackTrace(); e.printStackTrace();