mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
Merge remote-tracking branch 'origin/GP-4143_Dan_schemaAttributeAliasing--SQUASHED'
This commit is contained in:
commit
a416caf911
21 changed files with 584 additions and 158 deletions
|
@ -70,12 +70,10 @@ public class DefaultSchemaContext implements SchemaContext {
|
|||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof DefaultSchemaContext) {
|
||||
DefaultSchemaContext that = (DefaultSchemaContext) obj;
|
||||
if (obj instanceof DefaultSchemaContext that) {
|
||||
return Objects.equals(this.schemas, that.schemas);
|
||||
}
|
||||
if (obj instanceof SchemaContext) {
|
||||
SchemaContext that = (SchemaContext) obj;
|
||||
if (obj instanceof SchemaContext that) {
|
||||
return this.schemas.values().equals(that.getAllSchemas());
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -61,10 +61,9 @@ public class DefaultTargetObjectSchema
|
|||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof DefaultAttributeSchema)) {
|
||||
if (!(obj instanceof DefaultAttributeSchema that)) {
|
||||
return false;
|
||||
}
|
||||
DefaultAttributeSchema that = (DefaultAttributeSchema) obj;
|
||||
if (!Objects.equals(this.name, that.name)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -119,6 +118,64 @@ public class DefaultTargetObjectSchema
|
|||
}
|
||||
}
|
||||
|
||||
protected static class AliasResolver {
|
||||
private final Map<String, AttributeSchema> schemas;
|
||||
private final Map<String, String> aliases;
|
||||
private final AttributeSchema defaultSchema;
|
||||
private Map<String, String> resolvedAliases;
|
||||
|
||||
public AliasResolver(Map<String, AttributeSchema> schemas, Map<String, String> aliases,
|
||||
AttributeSchema defaultSchema) {
|
||||
this.schemas = schemas;
|
||||
this.aliases = aliases;
|
||||
this.defaultSchema = defaultSchema;
|
||||
}
|
||||
|
||||
public Map<String, String> resolveAliases() {
|
||||
this.resolvedAliases = new LinkedHashMap<>();
|
||||
for (String alias : aliases.keySet()) {
|
||||
if (alias.equals("")) {
|
||||
throw new IllegalArgumentException("Key '' cannot be an alias");
|
||||
}
|
||||
if (schemas.containsKey(alias)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Key '%s' cannot be both an attribute and an alias".formatted(alias));
|
||||
}
|
||||
resolveAlias(alias, new LinkedHashSet<>());
|
||||
}
|
||||
return resolvedAliases;
|
||||
}
|
||||
|
||||
protected String resolveAlias(String alias, LinkedHashSet<String> visited) {
|
||||
String already = resolvedAliases.get(alias);
|
||||
if (already != null) {
|
||||
return already;
|
||||
}
|
||||
if (!visited.add(alias)) {
|
||||
throw new IllegalArgumentException("Cycle of aliases: " + visited);
|
||||
}
|
||||
String to = aliases.get(alias);
|
||||
if (to == null) {
|
||||
return alias;
|
||||
}
|
||||
if (to.equals("")) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot alias to key '' (from %s)".formatted(alias));
|
||||
}
|
||||
String result = resolveAlias(to, visited);
|
||||
resolvedAliases.put(alias, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, AttributeSchema> resolveSchemas() {
|
||||
Map<String, AttributeSchema> resolved = new LinkedHashMap<>(schemas);
|
||||
for (Map.Entry<String, String> ent : resolvedAliases.entrySet()) {
|
||||
resolved.put(ent.getKey(), schemas.getOrDefault(ent.getValue(), defaultSchema));
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
private final SchemaContext context;
|
||||
private final SchemaName name;
|
||||
private final Class<?> type;
|
||||
|
@ -130,6 +187,7 @@ public class DefaultTargetObjectSchema
|
|||
private final ResyncMode elementResync;
|
||||
|
||||
private final Map<String, AttributeSchema> attributeSchemas;
|
||||
private final Map<String, String> attributeAliases;
|
||||
private final AttributeSchema defaultAttributeSchema;
|
||||
private final ResyncMode attributeResync;
|
||||
|
||||
|
@ -137,7 +195,8 @@ public class DefaultTargetObjectSchema
|
|||
Set<Class<? extends TargetObject>> interfaces, boolean isCanonicalContainer,
|
||||
Map<String, SchemaName> elementSchemas, SchemaName defaultElementSchema,
|
||||
ResyncMode elementResync,
|
||||
Map<String, AttributeSchema> attributeSchemas, AttributeSchema defaultAttributeSchema,
|
||||
Map<String, AttributeSchema> attributeSchemas, Map<String, String> attributeAliases,
|
||||
AttributeSchema defaultAttributeSchema,
|
||||
ResyncMode attributeResync) {
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
|
@ -149,7 +208,10 @@ public class DefaultTargetObjectSchema
|
|||
this.defaultElementSchema = defaultElementSchema;
|
||||
this.elementResync = elementResync;
|
||||
|
||||
this.attributeSchemas = Collections.unmodifiableMap(new LinkedHashMap<>(attributeSchemas));
|
||||
AliasResolver resolver =
|
||||
new AliasResolver(attributeSchemas, attributeAliases, defaultAttributeSchema);
|
||||
this.attributeAliases = Collections.unmodifiableMap(resolver.resolveAliases());
|
||||
this.attributeSchemas = Collections.unmodifiableMap(resolver.resolveSchemas());
|
||||
this.defaultAttributeSchema = defaultAttributeSchema;
|
||||
this.attributeResync = attributeResync;
|
||||
}
|
||||
|
@ -199,6 +261,11 @@ public class DefaultTargetObjectSchema
|
|||
return attributeSchemas;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributeAliases() {
|
||||
return attributeAliases;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeSchema getDefaultAttributeSchema() {
|
||||
return defaultAttributeSchema;
|
||||
|
@ -236,6 +303,7 @@ public class DefaultTargetObjectSchema
|
|||
sb.append("attributes(resync " + attributeResync + ") = ");
|
||||
sb.append(attributeSchemas);
|
||||
sb.append(" default " + defaultAttributeSchema);
|
||||
sb.append(" aliases " + attributeAliases);
|
||||
sb.append("\n}");
|
||||
}
|
||||
|
||||
|
@ -255,10 +323,9 @@ public class DefaultTargetObjectSchema
|
|||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof DefaultTargetObjectSchema)) {
|
||||
if (!(obj instanceof DefaultTargetObjectSchema that)) {
|
||||
return false;
|
||||
}
|
||||
DefaultTargetObjectSchema that = (DefaultTargetObjectSchema) obj;
|
||||
if (!Objects.equals(this.name, that.name)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -283,6 +350,9 @@ public class DefaultTargetObjectSchema
|
|||
if (!Objects.equals(this.attributeSchemas, that.attributeSchemas)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.attributeAliases, that.attributeAliases)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.defaultAttributeSchema, that.defaultAttributeSchema)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -191,6 +191,11 @@ public enum EnumerableTargetObjectSchema implements TargetObjectSchema {
|
|||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributeAliases() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeSchema getDefaultAttributeSchema() {
|
||||
return AttributeSchema.DEFAULT_VOID;
|
||||
|
|
|
@ -33,6 +33,7 @@ public class SchemaBuilder {
|
|||
private ResyncMode elementResync = TargetObjectSchema.DEFAULT_ELEMENT_RESYNC;
|
||||
|
||||
private Map<String, AttributeSchema> attributeSchemas = new LinkedHashMap<>();
|
||||
private Map<String, String> attributeAliases = new LinkedHashMap<>();
|
||||
private AttributeSchema defaultAttributeSchema = AttributeSchema.DEFAULT_ANY;
|
||||
private ResyncMode attributeResync = TargetObjectSchema.DEFAULT_ATTRIBUTE_RESYNC;
|
||||
|
||||
|
@ -163,10 +164,10 @@ public class SchemaBuilder {
|
|||
if (schema.getName().equals("")) {
|
||||
return setDefaultAttributeSchema(schema);
|
||||
}
|
||||
if (attributeSchemas.containsKey(schema.getName())) {
|
||||
throw new IllegalArgumentException("Duplicate attribute name '" + schema.getName() +
|
||||
"' origin1=" + attributeOrigins.get(schema.getName()) +
|
||||
" origin2=" + origin);
|
||||
if (attributeOrigins.containsKey(schema.getName())) {
|
||||
throw new IllegalArgumentException(
|
||||
"Duplicate attribute name '%s' adding schema origin1=%s origin2=%s".formatted(
|
||||
schema.getName(), attributeOrigins.get(schema.getName()), origin));
|
||||
}
|
||||
attributeSchemas.put(schema.getName(), schema);
|
||||
attributeOrigins.put(schema.getName(), origin);
|
||||
|
@ -178,6 +179,7 @@ public class SchemaBuilder {
|
|||
return setDefaultAttributeSchema(AttributeSchema.DEFAULT_ANY);
|
||||
}
|
||||
attributeSchemas.remove(name);
|
||||
attributeAliases.remove(name);
|
||||
attributeOrigins.remove(name);
|
||||
return this;
|
||||
}
|
||||
|
@ -194,11 +196,41 @@ public class SchemaBuilder {
|
|||
if (schema.getName().equals("")) {
|
||||
return setDefaultAttributeSchema(schema);
|
||||
}
|
||||
attributeAliases.remove(schema.getName());
|
||||
attributeSchemas.put(schema.getName(), schema);
|
||||
attributeOrigins.put(schema.getName(), origin);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void validateAlias(String from, String to) {
|
||||
if (from.equals("")) {
|
||||
throw new IllegalArgumentException("Key '' cannot be an alias");
|
||||
}
|
||||
if (to.equals("")) {
|
||||
throw new IllegalArgumentException("Cannot alias to key '' (from %s)".formatted(from));
|
||||
}
|
||||
}
|
||||
|
||||
public SchemaBuilder addAttributeAlias(String from, String to, Object origin) {
|
||||
validateAlias(from, to);
|
||||
if (attributeOrigins.containsKey(from)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Duplicate attribute name '%s' adding alias origin1=%s origin2=%s".formatted(
|
||||
from, attributeOrigins.get(from), origin));
|
||||
}
|
||||
attributeAliases.put(from, to);
|
||||
attributeOrigins.put(from, origin);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SchemaBuilder replaceAttributeAlias(String from, String to, Object origin) {
|
||||
validateAlias(from, to);
|
||||
attributeSchemas.remove(from);
|
||||
attributeAliases.put(from, to);
|
||||
attributeOrigins.put(from, origin);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SchemaBuilder setDefaultAttributeSchema(AttributeSchema defaultAttributeSchema) {
|
||||
this.defaultAttributeSchema = defaultAttributeSchema;
|
||||
return this;
|
||||
|
@ -227,6 +259,6 @@ public class SchemaBuilder {
|
|||
return new DefaultTargetObjectSchema(
|
||||
context, name, type, interfaces, isCanonicalContainer,
|
||||
elementSchemas, defaultElementSchema, elementResync,
|
||||
attributeSchemas, defaultAttributeSchema, attributeResync);
|
||||
attributeSchemas, attributeAliases, defaultAttributeSchema, attributeResync);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
|
||||
|
@ -42,6 +43,11 @@ import ghidra.util.Msg;
|
|||
* by matching on the keys (indices and names), the result being a subordinate
|
||||
* {@link TargetObjectSchema}. Keys must match exactly, unless the "pattern" is the empty string,
|
||||
* which matches any key. Similarly, the wild-card index is {@code []}.
|
||||
*
|
||||
* <p>
|
||||
* The schema can specify attribute aliases, which implies that a particular key ("from") will
|
||||
* always have the same value as another ("to"). As a result, the schemas of aliased keys will also
|
||||
* implicitly match.
|
||||
*/
|
||||
public interface TargetObjectSchema {
|
||||
public static final ResyncMode DEFAULT_ELEMENT_RESYNC = ResyncMode.NEVER;
|
||||
|
@ -107,9 +113,9 @@ public interface TargetObjectSchema {
|
|||
*
|
||||
* <p>
|
||||
* Each object specifies a element sync mode, and an attribute sync mode. These describe when
|
||||
* the client must call {@link TargetObject#resync(boolean, boolean)} to refresh/resync to
|
||||
* ensure it has a fresh cache of elements and/or attributes. Note that any client requesting a
|
||||
* resync will cause all clients to receive the updates.
|
||||
* the client must call {@link TargetObject#resync(RefreshBehavior, RefreshBehavior)} to
|
||||
* refresh/resync to ensure it has a fresh cache of elements and/or attributes. Note that any
|
||||
* client requesting a resync will cause all clients to receive the updates.
|
||||
*/
|
||||
enum ResyncMode {
|
||||
/**
|
||||
|
@ -332,10 +338,42 @@ public interface TargetObjectSchema {
|
|||
/**
|
||||
* Get the map of attribute names to named schemas
|
||||
*
|
||||
* <p>
|
||||
* The returned map will include aliases. To determine whether or not an attribute key is an
|
||||
* alias, check whether the entry's key matches the name of the attribute (see
|
||||
* {@link AttributeSchema#getName()}). It is possible the schema's name is empty, i.e., the
|
||||
* default schema. This indicates an alias to a key that was not named in the schema. Use
|
||||
* {@link #getAttributeAliases()} to determine the name of that key.
|
||||
*
|
||||
* @return the map
|
||||
*/
|
||||
Map<String, AttributeSchema> getAttributeSchemas();
|
||||
|
||||
/**
|
||||
* Get the map of attribute name aliases
|
||||
*
|
||||
* <p>
|
||||
* The returned map must provide the <em>direct</em> alias names. For any given key, the client
|
||||
* need only query the map once to determine the name of the attribute to which the alias
|
||||
* refers. Consequently, the map also cannot indicate a cycle.
|
||||
*
|
||||
* <p>
|
||||
* An aliased attribute takes the value of its target implicitly.
|
||||
*
|
||||
* @return the map
|
||||
*/
|
||||
Map<String, String> getAttributeAliases();
|
||||
|
||||
/**
|
||||
* Check if the given name is an alias and get the target attribute name
|
||||
*
|
||||
* @param name the name
|
||||
* @return the alias' target, or the given name if not an alias
|
||||
*/
|
||||
default String checkAliasedAttribute(String name) {
|
||||
return getAttributeAliases().getOrDefault(name, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default schema for attributes
|
||||
*
|
||||
|
@ -355,8 +393,8 @@ public interface TargetObjectSchema {
|
|||
* Get the attribute schema for a given attribute name
|
||||
*
|
||||
* <p>
|
||||
* If there's a schema specified for the given name, that schema is taken. Otherwise, the
|
||||
* default attribute schema is taken.
|
||||
* If there's a schema specified for the given name, that schema is taken. If the name refers to
|
||||
* an alias, its schema is taken. Otherwise, the default attribute schema is taken.
|
||||
*
|
||||
* @param name the name
|
||||
* @return the attribute schema
|
||||
|
@ -824,7 +862,7 @@ public interface TargetObjectSchema {
|
|||
*
|
||||
* @param type
|
||||
* @param path
|
||||
* @return
|
||||
* @return the predicates for finding objects
|
||||
*/
|
||||
default PathPredicates matcherForSuitable(Class<? extends TargetObject> type,
|
||||
List<String> path) {
|
||||
|
@ -947,7 +985,10 @@ public interface TargetObjectSchema {
|
|||
* Verify that the given value is of this schema's required type and, if applicable, implements
|
||||
* the required interfaces
|
||||
*
|
||||
* @param value the value
|
||||
* @param value the value being assigned to the key
|
||||
* @param parentPath the path of the object whose key is being assigned, for diagnostics
|
||||
* @param key the key that is being assigned
|
||||
* @param strict true to throw an exception upon violation; false to just log and continue
|
||||
*/
|
||||
default void validateTypeAndInterfaces(Object value, List<String> parentPath, String key,
|
||||
boolean strict) {
|
||||
|
|
|
@ -29,7 +29,25 @@ import ghidra.util.Msg;
|
|||
import ghidra.util.xml.XmlUtilities;
|
||||
|
||||
public class XmlSchemaContext extends DefaultSchemaContext {
|
||||
protected static final Set<String> TRUES = Set.of("true", "yes", "y", "1");
|
||||
protected static final String ELEM_CONTEXT = "context";
|
||||
protected static final String ATTR_CANONICAL = "canonical";
|
||||
protected static final String ELEM_SCHEMA = "schema";
|
||||
protected static final String ATTR_ELEMENT_RESYNC = "elementResync";
|
||||
protected static final String ATTR_ATTRIBUTE_RESYNC = "attributeResync";
|
||||
protected static final String ELEM_INTERFACE = "interface";
|
||||
protected static final String ELEM_ELEMENT = "element";
|
||||
protected static final String ATTR_INDEX = "index";
|
||||
protected static final String ELEM_ATTRIBUTE = "attribute";
|
||||
protected static final String ATTR_NAME = "name";
|
||||
protected static final String ATTR_SCHEMA = "schema";
|
||||
protected static final String ATTR_REQUIRED = "required";
|
||||
protected static final String ATTR_FIXED = "fixed";
|
||||
protected static final String ATTR_HIDDEN = "hidden";
|
||||
protected static final String ELEM_ATTRIBUTE_ALIAS = "attribute-alias";
|
||||
protected static final String ATTR_FROM = "from";
|
||||
protected static final String ATTR_TO = "to";
|
||||
protected static final String YES = "yes";
|
||||
protected static final Set<String> TRUES = Set.of("true", YES, "y", "1");
|
||||
|
||||
protected static boolean parseBoolean(Element ele, String attrName) {
|
||||
return TRUES.contains(ele.getAttributeValue(attrName, "no").toLowerCase());
|
||||
|
@ -40,7 +58,7 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
|||
}
|
||||
|
||||
public static Element contextToXml(SchemaContext ctx) {
|
||||
Element result = new Element("context");
|
||||
Element result = new Element(ELEM_CONTEXT);
|
||||
for (TargetObjectSchema schema : ctx.getAllSchemas()) {
|
||||
Element schemaElem = schemaToXml(schema);
|
||||
if (schemaElem != null) {
|
||||
|
@ -51,23 +69,30 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
|||
}
|
||||
|
||||
public static Element attributeSchemaToXml(AttributeSchema as) {
|
||||
Element attrElem = new Element("attribute");
|
||||
Element attrElem = new Element(ELEM_ATTRIBUTE);
|
||||
if (!as.getName().equals("")) {
|
||||
XmlUtilities.setStringAttr(attrElem, "name", as.getName());
|
||||
XmlUtilities.setStringAttr(attrElem, ATTR_NAME, as.getName());
|
||||
}
|
||||
XmlUtilities.setStringAttr(attrElem, "schema", as.getSchema().toString());
|
||||
XmlUtilities.setStringAttr(attrElem, ATTR_SCHEMA, as.getSchema().toString());
|
||||
if (as.isRequired()) {
|
||||
XmlUtilities.setStringAttr(attrElem, "required", "yes");
|
||||
XmlUtilities.setStringAttr(attrElem, ATTR_REQUIRED, YES);
|
||||
}
|
||||
if (as.isFixed()) {
|
||||
XmlUtilities.setStringAttr(attrElem, "fixed", "yes");
|
||||
XmlUtilities.setStringAttr(attrElem, ATTR_FIXED, YES);
|
||||
}
|
||||
if (as.isHidden()) {
|
||||
XmlUtilities.setStringAttr(attrElem, "hidden", "yes");
|
||||
XmlUtilities.setStringAttr(attrElem, ATTR_HIDDEN, YES);
|
||||
}
|
||||
return attrElem;
|
||||
}
|
||||
|
||||
public static Element aliasToXml(Map.Entry<String, String> alias) {
|
||||
Element aliasElem = new Element(ELEM_ATTRIBUTE_ALIAS);
|
||||
XmlUtilities.setStringAttr(aliasElem, ATTR_FROM, alias.getKey());
|
||||
XmlUtilities.setStringAttr(aliasElem, ATTR_TO, alias.getValue());
|
||||
return aliasElem;
|
||||
}
|
||||
|
||||
public static Element schemaToXml(TargetObjectSchema schema) {
|
||||
if (!TargetObject.class.isAssignableFrom(schema.getType())) {
|
||||
return null;
|
||||
|
@ -76,33 +101,40 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
|||
return null;
|
||||
}
|
||||
|
||||
Element result = new Element("schema");
|
||||
XmlUtilities.setStringAttr(result, "name", schema.getName().toString());
|
||||
Element result = new Element(ELEM_SCHEMA);
|
||||
XmlUtilities.setStringAttr(result, ATTR_NAME, schema.getName().toString());
|
||||
for (Class<? extends TargetObject> iface : schema.getInterfaces()) {
|
||||
Element ifElem = new Element("interface");
|
||||
XmlUtilities.setStringAttr(ifElem, "name", DebuggerObjectModel.requireIfaceName(iface));
|
||||
Element ifElem = new Element(ELEM_INTERFACE);
|
||||
XmlUtilities.setStringAttr(ifElem, ATTR_NAME,
|
||||
DebuggerObjectModel.requireIfaceName(iface));
|
||||
result.addContent(ifElem);
|
||||
}
|
||||
|
||||
if (schema.isCanonicalContainer()) {
|
||||
XmlUtilities.setStringAttr(result, "canonical", "yes");
|
||||
XmlUtilities.setStringAttr(result, ATTR_CANONICAL, YES);
|
||||
}
|
||||
XmlUtilities.setStringAttr(result, "elementResync",
|
||||
XmlUtilities.setStringAttr(result, ATTR_ELEMENT_RESYNC,
|
||||
schema.getElementResyncMode().name());
|
||||
XmlUtilities.setStringAttr(result, "attributeResync",
|
||||
XmlUtilities.setStringAttr(result, ATTR_ATTRIBUTE_RESYNC,
|
||||
schema.getAttributeResyncMode().name());
|
||||
|
||||
for (Map.Entry<String, SchemaName> ent : schema.getElementSchemas().entrySet()) {
|
||||
Element elemElem = new Element("element");
|
||||
XmlUtilities.setStringAttr(elemElem, "index", ent.getKey());
|
||||
XmlUtilities.setStringAttr(elemElem, "schema", ent.getValue().toString());
|
||||
Element elemElem = new Element(ELEM_ELEMENT);
|
||||
XmlUtilities.setStringAttr(elemElem, ATTR_INDEX, ent.getKey());
|
||||
XmlUtilities.setStringAttr(elemElem, ATTR_SCHEMA, ent.getValue().toString());
|
||||
result.addContent(elemElem);
|
||||
}
|
||||
Element deElem = new Element("element");
|
||||
XmlUtilities.setStringAttr(deElem, "schema", schema.getDefaultElementSchema().toString());
|
||||
Element deElem = new Element(ELEM_ELEMENT);
|
||||
XmlUtilities.setStringAttr(deElem, ATTR_SCHEMA,
|
||||
schema.getDefaultElementSchema().toString());
|
||||
result.addContent(deElem);
|
||||
|
||||
for (AttributeSchema as : schema.getAttributeSchemas().values()) {
|
||||
for (Map.Entry<String, AttributeSchema> ent : schema.getAttributeSchemas().entrySet()) {
|
||||
AttributeSchema as = ent.getValue();
|
||||
if (!ent.getKey().equals(as.getName())) {
|
||||
// Exclude aliases here
|
||||
continue;
|
||||
}
|
||||
Element attrElem = attributeSchemaToXml(as);
|
||||
result.addContent(attrElem);
|
||||
}
|
||||
|
@ -110,6 +142,12 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
|||
Element daElem = attributeSchemaToXml(das);
|
||||
result.addContent(daElem);
|
||||
|
||||
// Yes, these will be the "resolved" aliases, but I think that's okay.
|
||||
for (Map.Entry<String, String> alias : schema.getAttributeAliases().entrySet()) {
|
||||
Element aliasElem = aliasToXml(alias);
|
||||
result.addContent(aliasElem);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -138,7 +176,7 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
|||
|
||||
public static XmlSchemaContext contextFromXml(Element contextElem) {
|
||||
XmlSchemaContext ctx = new XmlSchemaContext();
|
||||
for (Element schemaElem : XmlUtilities.getChildren(contextElem, "schema")) {
|
||||
for (Element schemaElem : XmlUtilities.getChildren(contextElem, ELEM_SCHEMA)) {
|
||||
ctx.schemaFromXml(schemaElem);
|
||||
}
|
||||
return ctx;
|
||||
|
@ -153,16 +191,17 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
|||
private String requireAttributeValue(Element elem, String name) {
|
||||
String value = elem.getAttributeValue(name);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("Missing attribute " + name + " in " + elem);
|
||||
throw new IllegalArgumentException(
|
||||
"Missing attribute '%s' in %s".formatted(name, elem));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public TargetObjectSchema schemaFromXml(Element schemaElem) {
|
||||
SchemaBuilder builder = builder(name(schemaElem.getAttributeValue("name", "")));
|
||||
SchemaBuilder builder = builder(name(schemaElem.getAttributeValue(ATTR_NAME, "")));
|
||||
|
||||
for (Element ifaceElem : XmlUtilities.getChildren(schemaElem, "interface")) {
|
||||
String ifaceName = requireAttributeValue(ifaceElem, "name");
|
||||
for (Element ifaceElem : XmlUtilities.getChildren(schemaElem, ELEM_INTERFACE)) {
|
||||
String ifaceName = requireAttributeValue(ifaceElem, ATTR_NAME);
|
||||
Class<? extends TargetObject> iface = TargetObject.INTERFACES_BY_NAME.get(ifaceName);
|
||||
if (iface == null) {
|
||||
Msg.warn(this, "Unknown interface name: '" + ifaceName + "'");
|
||||
|
@ -172,29 +211,35 @@ public class XmlSchemaContext extends DefaultSchemaContext {
|
|||
}
|
||||
}
|
||||
|
||||
builder.setCanonicalContainer(parseBoolean(schemaElem, "canonical"));
|
||||
builder.setCanonicalContainer(parseBoolean(schemaElem, ATTR_CANONICAL));
|
||||
builder.setElementResyncMode(
|
||||
ResyncMode.valueOf(requireAttributeValue(schemaElem, "elementResync")));
|
||||
ResyncMode.valueOf(requireAttributeValue(schemaElem, ATTR_ELEMENT_RESYNC)));
|
||||
builder.setAttributeResyncMode(
|
||||
ResyncMode.valueOf(requireAttributeValue(schemaElem, "attributeResync")));
|
||||
ResyncMode.valueOf(requireAttributeValue(schemaElem, ATTR_ATTRIBUTE_RESYNC)));
|
||||
|
||||
for (Element elemElem : XmlUtilities.getChildren(schemaElem, "element")) {
|
||||
SchemaName schema = name(requireAttributeValue(elemElem, "schema"));
|
||||
String index = elemElem.getAttributeValue("index", "");
|
||||
for (Element elemElem : XmlUtilities.getChildren(schemaElem, ELEM_ELEMENT)) {
|
||||
SchemaName schema = name(requireAttributeValue(elemElem, ATTR_SCHEMA));
|
||||
String index = elemElem.getAttributeValue(ATTR_INDEX, "");
|
||||
builder.addElementSchema(index, schema, elemElem);
|
||||
}
|
||||
|
||||
for (Element attrElem : XmlUtilities.getChildren(schemaElem, "attribute")) {
|
||||
SchemaName schema = name(requireAttributeValue(attrElem, "schema"));
|
||||
boolean required = parseBoolean(attrElem, "required");
|
||||
boolean fixed = parseBoolean(attrElem, "fixed");
|
||||
boolean hidden = parseBoolean(attrElem, "hidden");
|
||||
for (Element attrElem : XmlUtilities.getChildren(schemaElem, ELEM_ATTRIBUTE)) {
|
||||
SchemaName schema = name(requireAttributeValue(attrElem, ATTR_SCHEMA));
|
||||
boolean required = parseBoolean(attrElem, ATTR_REQUIRED);
|
||||
boolean fixed = parseBoolean(attrElem, ATTR_FIXED);
|
||||
boolean hidden = parseBoolean(attrElem, ATTR_HIDDEN);
|
||||
|
||||
String name = attrElem.getAttributeValue("name", "");
|
||||
String name = attrElem.getAttributeValue(ATTR_NAME, "");
|
||||
builder.addAttributeSchema(
|
||||
new DefaultAttributeSchema(name, schema, required, fixed, hidden), attrElem);
|
||||
}
|
||||
|
||||
for (Element aliasElem : XmlUtilities.getChildren(schemaElem, ELEM_ATTRIBUTE_ALIAS)) {
|
||||
String from = requireAttributeValue(aliasElem, ATTR_FROM);
|
||||
String to = requireAttributeValue(aliasElem, ATTR_TO);
|
||||
builder.addAttributeAlias(from, to, aliasElem);
|
||||
}
|
||||
|
||||
return builder.buildAndAdd();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,22 +28,25 @@ import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema
|
|||
import ghidra.dbg.target.schema.TargetObjectSchema.*;
|
||||
|
||||
public class XmlTargetObjectSchemaTest {
|
||||
protected static final String SCHEMA_XML = "" +
|
||||
"<context>\n" +
|
||||
" <schema name=\"root\" canonical=\"yes\" elementResync=\"NEVER\" attributeResync=\"ONCE\">\n" +
|
||||
" <interface name=\"Process\" />\n" +
|
||||
" <interface name=\"Interpreter\" />\n" +
|
||||
" <element index=\"reserved\" schema=\"VOID\" />\n" +
|
||||
" <element schema=\"down1\" />\n" +
|
||||
" <attribute name=\"some_int\" schema=\"INT\" />\n" +
|
||||
" <attribute name=\"some_object\" schema=\"OBJECT\" required=\"yes\" fixed=\"yes\" hidden=\"yes\" />\n" +
|
||||
" <attribute schema=\"ANY\" hidden=\"yes\" />\n" +
|
||||
" </schema>\n" +
|
||||
" <schema name=\"down1\" elementResync=\"ALWAYS\" attributeResync=\"ALWAYS\">\n" +
|
||||
" <element schema=\"OBJECT\" />\n" +
|
||||
" <attribute schema=\"VOID\" fixed=\"yes\" hidden=\"yes\" />\n" +
|
||||
" </schema>\n" +
|
||||
"</context>";
|
||||
protected static final String SCHEMA_XML =
|
||||
// Do not line-wrap or serialize test will fail
|
||||
"""
|
||||
<context>
|
||||
<schema name="root" canonical="yes" elementResync="NEVER" attributeResync="ONCE">
|
||||
<interface name="Process" />
|
||||
<interface name="Interpreter" />
|
||||
<element index="reserved" schema="VOID" />
|
||||
<element schema="down1" />
|
||||
<attribute name="some_int" schema="INT" />
|
||||
<attribute name="some_object" schema="OBJECT" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
<attribute-alias from="_int" to="some_int" />
|
||||
</schema>
|
||||
<schema name="down1" elementResync="ALWAYS" attributeResync="ALWAYS">
|
||||
<element schema="OBJECT" />
|
||||
<attribute schema="VOID" fixed="yes" hidden="yes" />
|
||||
</schema>
|
||||
</context>"""; // Cannot have final final new-line or serialize test will fail
|
||||
|
||||
protected static final DefaultSchemaContext CTX = new DefaultSchemaContext();
|
||||
protected static final SchemaName NAME_ROOT = new SchemaName("root");
|
||||
|
@ -59,6 +62,7 @@ public class XmlTargetObjectSchemaTest {
|
|||
EnumerableTargetObjectSchema.INT.getName(), false, false, false), null)
|
||||
.addAttributeSchema(new DefaultAttributeSchema("some_object",
|
||||
EnumerableTargetObjectSchema.OBJECT.getName(), true, true, true), null)
|
||||
.addAttributeAlias("_int", "some_int", null)
|
||||
.setAttributeResyncMode(ResyncMode.ONCE)
|
||||
.buildAndAdd();
|
||||
protected static final TargetObjectSchema SCHEMA_DOWN1 = CTX.builder(NAME_DOWN1)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue