Java tutorial
/* * 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. * * Contributions from 2013-2017 where performed either by US government * employees, or under US Veterans Health Administration contracts. * * US Veterans Health Administration contributions by government employees * are work of the U.S. Government and are not subject to copyright * protection in the United States. Portions contributed by government * employees are USGovWork (17USC 105). Not subject to copyright. * * Contribution by contractors to the US Veterans Health Administration * during this period are contractually contributed under the * Apache License, Version 2.0. * * See: https://www.usa.gov/government-works * * Contributions prior to 2013: * * Copyright (C) International Health Terminology Standards Development Organisation. * Licensed under the Apache License, Version 2.0. * */ package sh.isaac.api; //~--- JDK imports ------------------------------------------------------------ import java.io.DataOutputStream; import java.io.IOException; import java.io.Writer; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Optional; import java.util.Stack; import java.util.TreeMap; import java.util.UUID; //~--- non-JDK imports -------------------------------------------------------- import org.jvnet.hk2.annotations.Contract; import sh.isaac.api.bootstrap.TermAux; import sh.isaac.api.commit.CommitService; import sh.isaac.api.component.concept.ConceptBuilder; import sh.isaac.api.component.concept.ConceptChronology; import sh.isaac.api.component.concept.ConceptService; import sh.isaac.api.component.concept.ConceptSpecification; import sh.isaac.api.component.concept.description.DescriptionBuilder; import sh.isaac.api.component.concept.description.DescriptionBuilderService; import sh.isaac.api.component.semantic.version.dynamic.DynamicColumnInfo; import sh.isaac.api.constants.DynamicConstants; import sh.isaac.api.constants.MetadataConceptConstant; import sh.isaac.api.constants.MetadataConceptConstantGroup; import sh.isaac.api.constants.MetadataDynamicConstant; import sh.isaac.api.externalizable.DataWriterService; import sh.isaac.api.externalizable.MultipleDataWriterService; import sh.isaac.api.logic.LogicalExpression; import sh.isaac.api.logic.LogicalExpressionBuilder; import sh.isaac.api.logic.LogicalExpressionBuilderService; import sh.isaac.api.util.UuidT5Generator; import static sh.isaac.api.logic.LogicalExpressionBuilder.And; import static sh.isaac.api.logic.LogicalExpressionBuilder.ConceptAssertion; import static sh.isaac.api.logic.LogicalExpressionBuilder.NecessarySet; import sh.isaac.api.component.semantic.version.MutableDescriptionVersion; import sh.isaac.api.component.semantic.SemanticChronology; import sh.isaac.api.component.semantic.SemanticBuilder; import sh.isaac.api.component.semantic.version.dynamic.DynamicData; import sh.isaac.api.component.semantic.version.dynamic.DynamicUtility; import sh.isaac.api.component.semantic.version.dynamic.types.DynamicArray; //~--- classes ---------------------------------------------------------------- /** * Class for programatically creating and exporting a taxonomy. * * @author kec */ @Contract public class IsaacTaxonomy { /** * The concept builders. */ private final TreeMap<String, ConceptBuilder> conceptBuilders = new TreeMap<>(); /** * The semantic builders. */ private final List<SemanticBuilder<?>> semanticBuilders = new ArrayList<>(); /** * The concept builders in insertion order. */ private final List<ConceptBuilder> conceptBuildersInInsertionOrder = new ArrayList<>(); /** * The parent stack. */ private final Stack<ConceptBuilder> parentStack = new Stack<>(); /** * The current. */ private ConceptBuilder current; /** * The module spec. */ private final ConceptSpecification moduleSpec; /** * The path spec. */ private final ConceptSpecification pathSpec; /** * The author spec. */ private final ConceptSpecification authorSpec; /** * The semantic tag. */ private final String semanticTag; //~--- constructors -------------------------------------------------------- /** * Instantiates a new isaac taxonomy. * * @param path the path * @param author the author * @param module the module * @param isaType the isa type * @param semanticTag the semantic tag */ public IsaacTaxonomy(ConceptSpecification path, ConceptSpecification author, ConceptSpecification module, ConceptSpecification isaType, String semanticTag) { this.pathSpec = path; this.authorSpec = author; this.moduleSpec = module; this.semanticTag = semanticTag; } //~--- methods ------------------------------------------------------------- /** * Creates the concept. * * @param cc the cc * @return the concept builder * @throws Exception the exception */ public final ConceptBuilder createConcept(MetadataConceptConstant cc) throws Exception { try { final ConceptBuilder cb = createConcept(cc.getPrimaryName(), (cc.getParent() != null) ? cc.getParent().getNid() : null, null); cb.setPrimordialUuid(cc.getUUID()); cc.getDefinitions().forEach((definition) -> { addDescription(definition, cb, TermAux.DEFINITION_DESCRIPTION_TYPE, false); }); cc.getSynonyms().forEach((definition) -> { addDescription(definition, cb, TermAux.REGULAR_NAME_DESCRIPTION_TYPE, false); }); if (cc instanceof MetadataConceptConstantGroup) { pushParent(current()); for (final MetadataConceptConstant nested : ((MetadataConceptConstantGroup) cc).getChildren()) { createConcept(nested); } popParent(); } if (cc instanceof MetadataDynamicConstant) { // See {@link DynamicSememeUsageDescription} class for more details on this format. final MetadataDynamicConstant dsc = (MetadataDynamicConstant) cc; final DescriptionBuilder<? extends SemanticChronology, ? extends MutableDescriptionVersion> db = addDescription( dsc.getAssemblageDescription(), cb, TermAux.DEFINITION_DESCRIPTION_TYPE, false); // Annotate the description as the 'special' type that means this concept is suitable for use as an assemblage concept SemanticBuilder<? extends SemanticChronology> sb = Get.semanticBuilderService() .getDynamicBuilder(db, DynamicConstants.get().DYNAMIC_DEFINITION_DESCRIPTION.getNid()); db.addSemantic(sb); if (dsc.getDynamicColumns() != null) { for (final DynamicColumnInfo col : dsc.getDynamicColumns()) { final DynamicData[] colData = LookupService.getService(DynamicUtility.class) .configureDynamicDefinitionDataForColumn(col); sb = Get.semanticBuilderService().getDynamicBuilder(cb, DynamicConstants.get().DYNAMIC_EXTENSION_DEFINITION.getNid(), colData); cb.addSemantic(sb); } } final DynamicData[] data = LookupService.getService(DynamicUtility.class) .configureDynamicRestrictionData(dsc.getReferencedComponentTypeRestriction(), dsc.getReferencedComponentSubTypeRestriction()); if (data != null) { sb = Get.semanticBuilderService().getDynamicBuilder(cb, DynamicConstants.get().DYNAMIC_SEMEME_REFERENCED_COMPONENT_RESTRICTION.getNid(), data); cb.addSemantic(sb); } final DynamicArray<DynamicData> indexConfig = LookupService.getService(DynamicUtility.class) .configureColumnIndexInfo(dsc.getDynamicColumns()); if (indexConfig != null) { sb = Get.semanticBuilderService().getDynamicBuilder(cb, DynamicConstants.get().DYNAMIC_INDEX_CONFIGURATION.getNid(), new DynamicData[] { indexConfig }); cb.addSemantic(sb); } } return cb; } catch (final Exception e) { throw new Exception("Problem with '" + cc.getPrimaryName() + "'", e); } } /** * Export. * * @param jsonPath the json path * @param ibdfPath the ibdf path * @throws IOException Signals that an I/O exception has occurred. */ public void export(Optional<Path> jsonPath, Optional<Path> ibdfPath) throws IOException { final long exportTime = System.currentTimeMillis(); final int stampSequence = Get.stampService().getStampSequence(Status.ACTIVE, exportTime, this.authorSpec.getNid(), this.moduleSpec.getNid(), this.pathSpec.getNid()); final CommitService commitService = Get.commitService(); final AssemblageService assemblageService = Get.assemblageService(); final ConceptService conceptService = Get.conceptService(); commitService.setComment(stampSequence, "Generated by maven from java sources"); this.conceptBuildersInInsertionOrder.forEach((builder) -> { buildAndWrite(builder, stampSequence, conceptService, assemblageService); }); this.semanticBuilders.forEach((builder) -> { buildAndWrite(builder, stampSequence, conceptService, assemblageService); }); final int stampAliasForPromotion = Get.stampService().getStampSequence(Status.ACTIVE, exportTime + (1000 * 60), this.authorSpec.getNid(), this.moduleSpec.getNid(), this.pathSpec.getNid()); commitService.addAlias(stampSequence, stampAliasForPromotion, "promoted by maven"); try (DataWriterService writer = new MultipleDataWriterService(jsonPath, ibdfPath)) { Get.isaacExternalizableStream().forEach((ochreExternalizable) -> writer.put(ochreExternalizable)); } } /** * Export java binding. * * @param out the out * @param packageName the package name * @param className the class name * @throws IOException Signals that an I/O exception has occurred. */ public void exportJavaBinding(Writer out, String packageName, String className) throws IOException { out.append("package " + packageName + ";\n"); out.append("\n\nimport sh.isaac.api.component.concept.ConceptSpecification;\n"); out.append("import sh.isaac.api.ConceptProxy;\n"); out.append("import java.util.UUID;\n"); out.append("\n\npublic class " + className + " {\n"); ArrayList<String> constantList = new ArrayList<>(); ArrayList<ConceptBuilder> sortedBuilders = new ArrayList<>(this.conceptBuildersInInsertionOrder); sortedBuilders.sort((o1, o2) -> { return o1.getFullySpecifiedDescriptionBuilder().getDescriptionText() .compareTo(o2.getFullySpecifiedDescriptionBuilder().getDescriptionText()); }); for (final ConceptBuilder concept : sortedBuilders) { final String preferredName = concept.getFullySpecifiedDescriptionBuilder().getDescriptionText(); String constantName = preferredName.toUpperCase(); constantName = constantName.replace(".", ""); constantName = constantName.replace(",", ""); constantName = constantName.replace("", ""); constantName = constantName.replace("", "C"); constantName = constantName.replace("(", "___"); constantName = constantName.replace(")", ""); constantName = constantName.replace(" ", "_"); constantName = constantName.replace("-", "_"); constantName = constantName.replace("+", "_PLUS"); constantName = constantName.replace("/", "_AND_OR_"); out.append("\n\n /** Java binding for the concept described as <strong><em>" + preferredName + "</em></strong>;\n * identified by UUID: {@code \n * " + "<a href=\"http://localhost:8080/terminology/rest/concept/" + concept.getPrimordialUuid() + "\">\n * " + concept.getPrimordialUuid() + "</a>}.*/"); constantList.add(constantName); out.append("\n public static final ConceptSpecification " + constantName + " ="); out.append("\n new ConceptProxy(\"" + preferredName + "\""); for (final UUID uuid : concept.getUuidList()) { out.append(", UUID.fromString(\"" + uuid.toString() + "\")"); } out.append(");"); } out.append("\n\n public static final ConceptSpecification[] META_DATA_CONCEPTS = {\n"); Collections.sort(constantList); for (int i = 0; i < constantList.size(); i++) { String constant = constantList.get(i); if (i != constantList.size() - 1) { out.append(" ").append(constant).append(",\n"); } else { out.append(" ").append(constant).append("\n"); } } out.append(" };"); out.append("\n}\n"); out.close(); } /** * Export yaml binding. * * @param out the out * @param packageName the package name * @param className the class name * @throws IOException Signals that an I/O exception has occurred. */ public void exportYamlBinding(Writer out, String packageName, String className) throws IOException { out.append("#YAML Bindings for " + packageName + "." + className + "\n"); // TODO use common code (when moved somewhere common) to extract the version number from the pom.xml out.append("#Generated " + new Date().toString() + "\n"); for (final ConceptBuilder concept : this.conceptBuildersInInsertionOrder) { final String preferredName = concept.getFullySpecifiedDescriptionBuilder().getDescriptionText(); String constantName = preferredName.toUpperCase(); constantName = constantName.replace("(", "\u01C1"); constantName = constantName.replace(")", "\u01C1"); constantName = constantName.replace(" ", "_"); constantName = constantName.replace("-", "_"); constantName = constantName.replace("+", "_PLUS"); constantName = constantName.replace("/", "_AND"); out.append("\n" + constantName + ":\n"); out.append(" fqn: " + preferredName + "\n"); out.append(" uuids:\n"); for (final UUID uuid : concept.getUuidList()) { out.append(" - " + uuid.toString() + "\n"); } } out.close(); } /** * Adds the path. * * @param pathAssemblageConcept the path assemblage concept * @param pathConcept the path concept * @param semanticUuid the UUID to assign to the path semantic... */ protected final void addPath(ConceptBuilder pathAssemblageConcept, ConceptBuilder pathConcept, UUID semanticUuid) { SemanticBuilder pathMemberBuilder = Get.semanticBuilderService() .getMembershipSemanticBuilder(pathConcept.getNid(), pathAssemblageConcept.getNid()); pathMemberBuilder.setPrimordialUuid(semanticUuid); this.semanticBuilders.add(pathMemberBuilder); } /** * Creates the concept. * * @param specification the concept specification * @return the concept builder */ protected final ConceptBuilder createConcept(ConceptSpecification specification) { //ConceptProxy specification = (ConceptProxy) spec; final ConceptBuilder builder = createConcept(specification.getFullySpecifiedConceptDescriptionText()); if (specification.getPrimordialUuid().version() == 4) { throw new UnsupportedOperationException("ERROR: must not use type 4 uuid for: " + specification.getFullySpecifiedConceptDescriptionText()); } builder.setPrimordialUuid(specification.getUuidList().get(0)); if (specification.getUuidList().size() > 1) { builder.addUuids(specification.getUuidList().subList(1, specification.getUuidList().size()) .toArray(new UUID[0])); } if (specification instanceof ConceptProxy) { Optional<String> preferredDescription = ((ConceptProxy) specification) .getPreferedConceptDescriptionTextNoLookup(); if (preferredDescription.isPresent()) { builder.getPreferredDescriptionBuilder().setDescriptionText(preferredDescription.get()); } } else { Optional<String> preferredDescription = specification.getPreferedConceptDescriptionText(); if (preferredDescription.isPresent()) { builder.getPreferredDescriptionBuilder().setDescriptionText(preferredDescription.get()); } } return builder; } /** * Creates the concept. * * @param name the name * @return the concept builder */ protected final ConceptBuilder createConcept(String name) { return createConcept(name, null, null); } /** * Creates the concept. * * @param name the name * @param nonPreferredSynonym the non preferred synonym * @return the concept builder */ protected final ConceptBuilder createConcept(String name, String nonPreferredSynonym) { return createConcept(name, null, nonPreferredSynonym); } /** * If parent is provided, it ignores the parent stack, and uses the provided parent instead. If parent is not * provided, it uses the parentStack (if populated), otherwise, it creates the concept without setting a parent. * * @param name the name * @param parentId the parent id * @param nonPreferredSynonym the non preferred synonym * @return the concept builder */ protected final ConceptBuilder createConcept(String name, Integer parentId, String nonPreferredSynonym) { checkConceptDescriptionText(name); if (this.parentStack.isEmpty() && (parentId == null)) { this.current = Get.conceptBuilderService().getDefaultConceptBuilder(name, this.semanticTag, null); } else { final LogicalExpressionBuilderService expressionBuilderService = LookupService .getService(LogicalExpressionBuilderService.class); final LogicalExpressionBuilder defBuilder = expressionBuilderService.getLogicalExpressionBuilder(); NecessarySet(And(ConceptAssertion( (parentId == null) ? this.parentStack.lastElement().getNid() : parentId, defBuilder))); final LogicalExpression logicalExpression = defBuilder.build(); this.current = Get.conceptBuilderService().getDefaultConceptBuilder(name, this.semanticTag, logicalExpression); } if (org.apache.commons.lang3.StringUtils.isNotBlank(nonPreferredSynonym)) { this.current.addDescription(nonPreferredSynonym, TermAux.REGULAR_NAME_DESCRIPTION_TYPE); } this.conceptBuilders.put(name, this.current); this.conceptBuildersInInsertionOrder.add(this.current); return this.current; } /** * Current. * * @return the concept builder */ protected final ConceptBuilder current() { return this.current; } /** * Export. * * @param dataOutputStream the data output stream */ protected void export(DataOutputStream dataOutputStream) { throw new UnsupportedOperationException("Not supported yet."); // To change body of generated methods, choose Tools | Templates. } /** * Iterator over all of the concept builders, and 'fix' any that were entered without having their primordial UUID * set to a consistent value. The builder assigned a Type4 (random) UUID the first time that getPrimordialUuid() is * called - must override that UUID with one that we can consistently create upon each execution that builds the * MetaData constants. */ protected final void generateStableUUIDs() { this.conceptBuilders.values().forEach((cb) -> { ensureStableUUID(cb); }); } /** * Pop parent. */ protected final void popParent() { this.parentStack.pop(); } /** * Push parent. * * @param parent the parent */ protected final void pushParent(ConceptBuilder parent) { ensureStableUUID(parent); // no generated UUIDs from this point on.... this.parentStack.push(parent); } /** * type should be either {@link TermAux#DEFINITION_DESCRIPTION_TYPE} or {@link TermAux#REGULAR_NAME_DESCRIPTION_TYPE} This * currently only creates english language descriptions. * * @param description the description * @param cb the cb * @param descriptionType the description type * @param preferred the preferred * @return the description builder */ private DescriptionBuilder<? extends SemanticChronology, ? extends MutableDescriptionVersion> addDescription( String description, ConceptBuilder cb, ConceptSpecification descriptionType, boolean preferred) { final DescriptionBuilder<? extends SemanticChronology, ? extends MutableDescriptionVersion> db = LookupService .getService(DescriptionBuilderService.class) .getDescriptionBuilder(description, cb, descriptionType, TermAux.ENGLISH_LANGUAGE); if (preferred) { db.addPreferredInDialectAssemblage(TermAux.US_DIALECT_ASSEMBLAGE); } else { db.addAcceptableInDialectAssemblage(TermAux.US_DIALECT_ASSEMBLAGE); } cb.addDescription(db); return db; } /** * Builds the and write. * * @param builder the builder * @param stampCoordinate the stamp coordinate * @param conceptService the concept service * @param assemblageService the assemblage service * @throws IllegalStateException the illegal state exception */ private void buildAndWrite(IdentifiedComponentBuilder builder, int stampCoordinate, ConceptService conceptService, AssemblageService assemblageService) throws IllegalStateException { final List<?> builtObjects = new ArrayList<>(); builder.build(stampCoordinate, builtObjects); builtObjects.forEach((builtObject) -> { if (builtObject instanceof ConceptChronology) { conceptService.writeConcept((ConceptChronology) builtObject); ConceptChronology restored = conceptService .getConceptChronology(((ConceptChronology) builtObject).getNid()); if (restored.getAssemblageNid() >= 0) { System.out.println("Bad restore of: " + restored); } } else if (builtObject instanceof SemanticChronology) { assemblageService.writeSemanticChronology((SemanticChronology) builtObject); } else { throw new UnsupportedOperationException("b Can't handle: " + builtObject); } }); } /** * Check concept description text. * * @param name the name */ private void checkConceptDescriptionText(String name) { if (this.conceptBuilders.containsKey(name)) { throw new RuntimeException("Concept is already added: " + name); } } /** * Ensure stable UUID. * * @param builder the builder */ private void ensureStableUUID(ConceptBuilder builder) { if (builder.getPrimordialUuid().version() == 4) { builder.setPrimordialUuid(UuidT5Generator.get(UuidT5Generator.PATH_ID_FROM_FS_DESC, builder.getFullySpecifiedConceptDescriptionText())); } } }