Java tutorial
/** * WRML - Web Resource Modeling Language * __ __ ______ __ __ __ * /\ \ _ \ \ /\ == \ /\ "-./ \ /\ \ * \ \ \/ ".\ \\ \ __< \ \ \-./\ \\ \ \____ * \ \__/".~\_\\ \_\ \_\\ \_\ \ \_\\ \_____\ * \/_/ \/_/ \/_/ /_/ \/_/ \/_/ \/_____/ * * http://www.wrml.org * * Copyright (C) 2011 - 2013 Mark Masse <mark@wrml.org> (OSS project WRML.org) * * 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 org.wrml.runtime.format; import org.apache.commons.lang3.reflect.TypeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wrml.model.Model; import org.wrml.model.rest.Document; import org.wrml.model.rest.Embedded; import org.wrml.model.schema.ValueType; import org.wrml.runtime.Context; import org.wrml.runtime.Dimensions; import org.wrml.runtime.DimensionsBuilder; import org.wrml.runtime.Keys; import org.wrml.runtime.schema.PropertyProtoSlot; import org.wrml.runtime.schema.ProtoSlot; import org.wrml.runtime.schema.Prototype; import org.wrml.runtime.schema.SchemaLoader; import org.wrml.runtime.syntax.SyntaxHandler; import org.wrml.runtime.syntax.SyntaxLoader; import java.lang.reflect.Type; import java.net.URI; import java.util.*; /** * An abstract representation of a serialized graph of WRML models, which sort * of resembles a DOM. */ public final class ParserModelGraph extends ModelGraph { private static final Logger LOG = LoggerFactory.getLogger(ParserModelGraph.class); /** * The keys to apply to the graph's root model. */ private final Keys _RootModelKeys; /** * The dimensions to apply to the graph's root model. */ private final DimensionsBuilder _RootModelDimensionsBuilder; /** * Follows the scope (nesting/indentation) of the graph's models (sets of * slots), and lists (of models or other "raw" types). */ private final LinkedList<ScopeType> _ScopeStack; /** * Follows the focus of the graph's models. */ private final LinkedList<Model> _ModelStack; /** * Follows the focus of the graph's (REST) Document models. */ private final LinkedList<Model> _DocumentStack; /** * Follows the bread-crumb-like scope of slots. */ private final LinkedList<String> _SlotNameStack; /** * Follows the nesting of lists. */ private final LinkedList<List<Object>> _ListStack; /** * Follows the model dimensions. */ private final LinkedList<DimensionsBuilder> _DimensionsBuilderStack; private final Map<UUID, Model> _ShortcutModels; /** * Create a new model graph. * * @param context The context to operate within. * @param rootModelDimensions The dimensions to use for the root model. */ public ParserModelGraph(final Context context, final Keys rootModelKeys, final Dimensions rootModelDimensions) { super(context); if (rootModelDimensions == null) { throw new ModelGraphException("The root model dimensions cannot be null.", null, this); } _RootModelDimensionsBuilder = new DimensionsBuilder(rootModelDimensions); _RootModelKeys = rootModelKeys; // Create the graph's focus tracking stacks _ScopeStack = new LinkedList<ScopeType>(); _ModelStack = new LinkedList<Model>(); _DocumentStack = new LinkedList<Model>(); _SlotNameStack = new LinkedList<String>(); _ListStack = new LinkedList<List<Object>>(); _DimensionsBuilderStack = new LinkedList<DimensionsBuilder>(); _ShortcutModels = new HashMap<UUID, Model>(); } public Object endList() { final List<Object> list = _ListStack.pop(); _ScopeStack.pop(); return endValue(list); } public Model endModel() { final Model model = _ModelStack.pop(); _ScopeStack.pop(); if (model instanceof Document) { _DocumentStack.pop(); } return (Model) endValue(model); } /** * Process a "raw" value (from a serialized model graph) into one that is * ready for the runtime. * * @param rawValue The "raw" value to process. A raw value is used in * serialization of model graphs. * @return The processed value, ready for runtime. */ @SuppressWarnings("unchecked") public Object endValue(final Object rawValue) { /* * As a special case for in-line model typing, WRML allows the * "schemaUri" slot to be present "on the wire". This slot is * "dimensional" in nature, so we will store it in the dimensions * instead of the model itself. * * Compare the slot's name and update the Dimensions accordingly. */ if (!_SlotNameStack.isEmpty() && _SlotNameStack.peek().equals(Model.SLOT_NAME_SCHEMA_URI)) { _DimensionsBuilderStack.peek().setSchemaUri(URI.create(String.valueOf(rawValue))); _SlotNameStack.pop(); return null; } Object runtimeValue = null; boolean isModel = false; if (rawValue != null && rawValue instanceof Model) { isModel = true; /* * Convert "raw" (undefined) model values here to handle the root * model case. */ runtimeValue = convertRawModelValue((Model) rawValue); } if (!_ScopeStack.isEmpty()) { if (rawValue != null) { if (TypeUtils.isInstance(rawValue, String.class)) { runtimeValue = convertRawStringValue((String) rawValue); } else if (TypeUtils.isInstance(rawValue, Integer.class)) { runtimeValue = convertRawIntegerValue((Integer) rawValue); } else if (TypeUtils.isInstance(rawValue, List.class)) { runtimeValue = convertRawListValue((List<Object>) rawValue); } else if (!isModel) { /* * The other "raw" types are equivalent to their "runtime" type * counterparts; no conversion necessary. */ runtimeValue = rawValue; } } final ScopeType scopeType = _ScopeStack.peek(); switch (scopeType) { case Model: { /* * The graph is focused on the parent model; meaning that a slot * should be set. */ final Model parentModel = _ModelStack.peek(); final String slotName = _SlotNameStack.peek(); if (!slotName.equals(Model.SLOT_NAME_HEAP_ID)) { parentModel.getSlotMap().put(slotName, runtimeValue); } else if (runtimeValue != null) { // Remove the started model from the stack _ModelStack.pop(); // Push this model onto the stack final Model m = (Model) runtimeValue; _ModelStack.push(m); } /* * Now finished with this slot, shift focus. */ _SlotNameStack.pop(); break; } case List: { /* * The graph is focused on a list; meaning that an element * should be added. */ final List<Object> list = _ListStack.peek(); list.add(runtimeValue); break; } } // End of switch } return runtimeValue; } public Keys getRootModelKeys() { return _RootModelKeys; } /** * Create a new list and make it the focus of this graph. Note that lists * may not start graphs, so you must create a model and a slot before you * may create any graph lists. */ public void startList() { if (_ScopeStack.isEmpty() || _ModelStack.isEmpty() || _SlotNameStack.isEmpty()) { // TODO: Future, move hard-coded messages like this into a // "StringTable & MessageFormat" utility class final ModelGraphException e = new ModelGraphException( "Graph lists may be created for a slot value of a model or as an element of another graph list; but the whole graph must be model-rooted.", this); ParserModelGraph.LOG.error(e.getMessage(), e); throw e; } final List<Object> newList = new ArrayList<>(); // Give the new list focus _ListStack.push(newList); _ScopeStack.push(ScopeType.List); } /** * Create a new model and make it the focus of this graph. */ public void startModel() { final Context context = getContext(); /* * Create a new model, with an "undefined" schema. */ final Model newModel = context.getModelBuilder().newModel(); DimensionsBuilder newModelDimensionsBuilder = null; if (_ScopeStack.isEmpty()) { /* * This is the first model in the graph; the root model. The first * model's creation is how a graph begins. */ /* * Use the specified root model's dimensions for this model. * * Note that the root model's dimensions may have a requested schema * URI already set. * * If however, during the course of creating this model's slots, the * graph discovers a "schemaUri" slot then we will trust the model to * tell us it's type. */ newModelDimensionsBuilder = _RootModelDimensionsBuilder; } else { /* * Make an effort to find the best "default" schema id for the new * (child/nested) model by consulting the focused slot's prototype * (if available). * * NOTE: If we have any scope at all, then the graph's rules state * that we must also have a parent model ("parent" in the model * graph hierarchy sense; not to be confused with schematic * inheritance and "base" schemas). */ final String slotName = _SlotNameStack.peek(); final DimensionsBuilder parentDimensionsBuilder = _DimensionsBuilderStack.peek(); /* * Initialize the new dimensions from the (graph's) parent * dimensions */ newModelDimensionsBuilder = new DimensionsBuilder(parentDimensionsBuilder.toDimensions()) .setSchemaUri(null); URI newModelSchemaUri = null; final URI parentSchemaUri = parentDimensionsBuilder.getSchemaUri(); if (parentSchemaUri != null) { /* * If the parent's schema URI is known, attempt to use it to * determine the (default) schema URI of new model. */ final SchemaLoader schemaLoader = context.getSchemaLoader(); final Prototype parentPrototype = schemaLoader.getPrototype(parentSchemaUri); if (parentPrototype.getLinkRelationUri(slotName) != null) { newModelSchemaUri = schemaLoader.getTypeUri(ValueType.JAVA_TYPE_LINK); } else { final ProtoSlot parentSlot = parentPrototype.getProtoSlot(slotName); if (parentSlot != null) { /* * The prototype slot associated with the parent model's * schema may help us determine the default schematic * type identity for the model we are about to create. */ final ScopeType scopeType = _ScopeStack.peek(); switch (scopeType) { case Model: { if (parentSlot.getValueType() == ValueType.Model) { newModelSchemaUri = ((PropertyProtoSlot) parentSlot).getModelSchemaUri(); } break; } case List: { if (parentSlot.getValueType() == ValueType.List) { final Type listElementType = ((PropertyProtoSlot) parentSlot).getListElementType(); if (ValueType.isModelType(listElementType)) { newModelSchemaUri = ((PropertyProtoSlot) parentSlot).getListElementSchemaUri(); } } break; } } // End of switch } } } newModelDimensionsBuilder.setSchemaUri(newModelSchemaUri); } /* * Shift the graph's focus to the new model. */ final Dimensions newModelDimensions = newModelDimensionsBuilder.toDimensions(); // newModel.setDimensions(newModelDimensions); _ModelStack.push(newModel); _DimensionsBuilderStack.push(newModelDimensionsBuilder); _ScopeStack.push(ScopeType.Model); final SchemaLoader schemaLoader = context.getSchemaLoader(); final URI schemaUri = newModelDimensions.getSchemaUri(); if (schemaUri != null) { Class<?> schemaInterface; try { schemaInterface = schemaLoader.getSchemaInterface(schemaUri); } catch (final ClassNotFoundException e) { ParserModelGraph.LOG.error(e.getMessage(), e); throw new ModelGraphException(e.getMessage(), e, this); } if (TypeUtils.isAssignable(schemaInterface, Document.class)) { _DocumentStack.push(newModel); } } } /** * Create a new slot (must be called with model focus). * * @param slotName the name of the slot to create storage for within the graph's * focused model. */ public void startSlot(final String slotName) { if (_ScopeStack.isEmpty() || _ModelStack.isEmpty() || (_ScopeStack.peek() != ScopeType.Model)) { // TODO: Future, move hard-coded messages like this into a // "StringTable & MessageFormat" utility class final ModelGraphException e = new ModelGraphException("Slot must be created within a model's scope.", this); ParserModelGraph.LOG.error(e.getMessage(), e); throw e; } _SlotNameStack.push(slotName); } private Object convertRawIntegerValue(final Integer rawValue) { final DimensionsBuilder dimensionsBuilder = _DimensionsBuilderStack.peek(); final URI modelSchemaUri = dimensionsBuilder.getSchemaUri(); if (modelSchemaUri == null) { return rawValue; } final String slotName = _SlotNameStack.peek(); final Context context = getContext(); final SchemaLoader schemaLoader = context.getSchemaLoader(); final Prototype prototype = schemaLoader.getPrototype(modelSchemaUri); final ProtoSlot protoSlot = prototype.getProtoSlot(slotName); final Type slotType = protoSlot.getHeapValueType(); if (slotType.equals(Integer.class) || (slotType.equals(int.class) && rawValue != null)) { return rawValue; } final ScopeType scopeType = _ScopeStack.peek(); switch (scopeType) { case Model: { if (slotType.equals(Long.class) || (slotType.equals(long.class) && rawValue != null)) { return new Long(rawValue); } if (slotType.equals(Double.class) || (slotType.equals(double.class) && rawValue != null)) { return new Double(rawValue); } break; } case List: { if (protoSlot.getValueType() == ValueType.List) { final Type listElementType = ((PropertyProtoSlot) protoSlot).getListElementType(); if (listElementType.equals(Long.class) || (listElementType.equals(long.class) && rawValue != null)) { return new Long(rawValue); } if (listElementType.equals(Double.class) || (listElementType.equals(double.class) && rawValue != null)) { return new Double(rawValue); } } break; } } // End of switch final ModelGraphException e = new ModelGraphException( "Unable to transform schema (" + modelSchemaUri + ") slot named \"" + slotName + "\" raw String value \"" + rawValue + "\" to type " + slotType + ".", this); ParserModelGraph.LOG.error(e.getMessage(), e); throw e; } private Object convertRawListValue(final List<Object> rawValue) { return rawValue; } private Object convertRawModelValue(final Model model) { final DimensionsBuilder dimensionsBuilder = _DimensionsBuilderStack.pop(); final URI schemaUri = dimensionsBuilder.getSchemaUri(); if (schemaUri == null || model == null) { return model; } final Dimensions dimensions = dimensionsBuilder.toDimensions(); final Model typedModel = model.newAlternate(dimensions); if (typedModel instanceof Embedded) { final Model documentModel = _DocumentStack.peek(); if (documentModel == null) { final ModelGraphException e = new ModelGraphException( "Model: " + typedModel + " must be embedded within a Document.", this); ParserModelGraph.LOG.error(e.getMessage(), e); throw e; } // Embedded models need a pointer to their enclosing Document. // Set the Embedded's document uri slot final URI uri = (URI) documentModel.getSlotMap().get(Document.SLOT_NAME_URI); typedModel.setSlotValue(Embedded.SLOT_NAME_DOCUMENT_URI, uri); } if (_ModelStack.isEmpty()) { // This is the root model, include all of its requested key values in the slot map. final Keys keys = getRootModelKeys(); if (keys != null) { typedModel.initKeySlots(keys); } } return typedModel; } @SuppressWarnings({ "unchecked", "rawtypes" }) private Object convertRawStringValue(final String rawValue) { final DimensionsBuilder dimensionsBuilder = _DimensionsBuilderStack.peek(); final URI schemaUri = dimensionsBuilder.getSchemaUri(); if (schemaUri == null) { return rawValue; } final ScopeType scopeType = _ScopeStack.peek(); final String slotName = _SlotNameStack.peek(); // Check if the slotName is heapId if (slotName.equals(Model.SLOT_NAME_HEAP_ID) && scopeType.equals(ScopeType.Model)) { LOG.debug("Current slot is a Heap Id {}", rawValue); final UUID heapId = UUID.fromString(rawValue); if (_ShortcutModels.containsKey(heapId)) { // Need to stuff this into the current model location.... return _ShortcutModels.get(heapId); } else { // Assumes scope type is Model LOG.debug("Creating new model with schema {} with heapId {}", new Object[] { schemaUri, heapId }); // Pull the created reference from the model stack and put into the map final Model parentModel = _ModelStack.peek(); _ShortcutModels.put(heapId, parentModel); // Return null if we're in the first occurrence return null; } } final Context context = getContext(); final SchemaLoader schemaLoader = context.getSchemaLoader(); final SyntaxLoader syntaxLoader = context.getSyntaxLoader(); final Prototype prototype = schemaLoader.getPrototype(schemaUri); // TODO fix this for HEAP ID's { final ProtoSlot protoSlot = prototype.getProtoSlot(slotName); final Type slotType = protoSlot.getHeapValueType(); if (slotType.equals(String.class)) { return rawValue; } switch (scopeType) { case Model: { if (TypeUtils.isAssignable(slotType, Enum.class)) { return Enum.valueOf((Class<Enum>) slotType, rawValue); } else if (slotType instanceof Class<?>) { final SyntaxHandler<?> syntaxHandler = syntaxLoader.getSyntaxHandler((Class<?>) slotType); if (syntaxHandler != null) { return syntaxHandler.parseSyntacticText(rawValue); } } break; } case List: { if (protoSlot.getValueType() == ValueType.List) { final Type listElementType = ((PropertyProtoSlot) protoSlot).getListElementType(); if (String.class.equals(listElementType)) { return rawValue; } else if (TypeUtils.isAssignable(listElementType, Enum.class)) { return Enum.valueOf((Class<Enum>) listElementType, rawValue); } else if (listElementType instanceof Class<?>) { final SyntaxHandler<?> syntaxHandler = syntaxLoader .getSyntaxHandler((Class<?>) listElementType); if (syntaxHandler != null) { return syntaxHandler.parseSyntacticText(rawValue); } } } break; } } // End of switch final ModelGraphException e = new ModelGraphException( "Unable to transform schema (" + schemaUri + ") slot named \"" + slotName + "\" raw String value \"" + rawValue + "\" to type " + slotType + ".", this); ParserModelGraph.LOG.error(e.getMessage(), e); throw e; } public enum ScopeType { Model, List; } }