package org.obolibrary.oboformat.model; import static org.semanticweb.owlapi.util.OWLAPIPreconditions.verifyNotNull; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.Nullable; import org.obolibrary.obo2owl.OboInOwlCardinalityTools; import org.obolibrary.oboformat.parser.OBOFormatConstants.OboFormatTag; /** * The Class Frame. */ public class Frame { /** * The clauses. */ protected Collection<Clause> clauses = new ArrayList<>(); /** * The id. */ @Nullable protected String id; /** * The type. */ @Nullable protected FrameType type; /** * Instantiates a new frame. */ public Frame() { this(null); } /** * Instantiates a new frame. * * @param type the type */ public Frame(@Nullable FrameType type) { this.type = type; } /** * freezing a frame signals that a frame has become quiescent, and that data structures can be * adjusted to increase performance or reduce memory consumption. If a frozen frame is * subsequently modified it will be thawed as necessary. */ public void freeze() { if (clauses.isEmpty()) { clauses = Collections.emptyList(); return; } for (Clause clause : clauses) { clause.freeze(); } if (clauses.size() == 1) { clauses = Collections.singletonList(clauses.iterator().next()); return; } if (clauses instanceof ArrayList<?>) { ArrayList<?> arrayList = (ArrayList<?>) clauses; arrayList.trimToSize(); } } /** * @return the type */ @Nullable public FrameType getType() { return type; } /** * @param type the new type */ public void setType(FrameType type) { this.type = type; } /** * @return the id */ @Nullable public String getId() { return id; } /** * @param id the new id */ public void setId(String id) { this.id = id; } /** * @return the clauses */ public Collection<Clause> getClauses() { return clauses; } /** * @param clauses the new clauses */ public void setClauses(Collection<Clause> clauses) { this.clauses = clauses; } /** * @param tag the tag * @return the clauses for tag */ public List<Clause> getClauses(@Nullable String tag) { List<Clause> cls = new ArrayList<>(); if (tag == null) { return cls; } for (Clause cl : clauses) { if (tag.equals(cl.getTag())) { cls.add(cl); } } return cls; } /** * @param tag the tag * @return the clauses for tag */ public List<Clause> getClauses(OboFormatTag tag) { return getClauses(tag.getTag()); } /** * @param tag the tag * @return null if no value set, otherwise first value */ @Nullable public Clause getClause(@Nullable String tag) { if (tag == null) { return null; } for (Clause cl : clauses) { if (tag.equals(cl.getTag())) { return cl; } // TODO - throw exception if more than one clause of this type? } return null; } /** * @param tag the tag * @return the clause for tag */ @Nullable public Clause getClause(OboFormatTag tag) { return getClause(tag.getTag()); } /** * @param cl the clause */ public void addClause(Clause cl) { if (!(clauses instanceof ArrayList)) { Collection<Clause> tmp = new ArrayList<>(clauses.size() + 1); tmp.addAll(clauses); clauses = tmp; } clauses.add(cl); } @Override public String toString() { StringBuilder sb = new StringBuilder("Frame("); sb.append(id); sb.append(' '); clauses.forEach(cl -> sb.append(cl).append(' ')); sb.append(')'); return sb.toString(); } /** * @param tag the tag * @return the tag value for tag */ @Nullable public Object getTagValue(String tag) { Clause clause = getClause(tag); if (clause == null) { return null; } return clause.getValue(); } /** * @param tag the tag * @return the tag value for tag */ @Nullable public Object getTagValue(OboFormatTag tag) { return getTagValue(tag.getTag()); } /** * @param <T> the generic type * @param tag the tag * @param cls the cls * @return the tag value for tag and class */ @Nullable public <T> T getTagValue(String tag, Class<T> cls) { Clause clause = getClause(tag); if (clause == null) { return null; } Object value = clause.getValue(); if (value.getClass().isAssignableFrom(cls)) { return cls.cast(value); } return null; } /** * @param <T> the generic type * @param tag the tag * @param cls the cls * @return the tag value for tag and class */ @Nullable public <T> T getTagValue(OboFormatTag tag, Class<T> cls) { return getTagValue(tag.getTag(), cls); } /** * @param tag the tag * @return the tag values for tag */ public Collection<Object> getTagValues(OboFormatTag tag) { return getTagValues(tag.getTag()); } /** * @param tag the tag * @return the tag values for tag */ public Collection<Object> getTagValues(String tag) { Collection<Object> vals = new ArrayList<>(); getClauses(tag).forEach(v -> vals.add(v.getValue())); return vals; } /** * @param <T> the generic type * @param tag the tag * @param cls the cls * @return the tag values for tag and class */ public <T> Collection<T> getTagValues(OboFormatTag tag, Class<T> cls) { return getTagValues(tag.getTag(), cls); } /** * @param <T> the generic type * @param tag the tag * @param cls the cls * @return the tag values for tag and class */ public <T> Collection<T> getTagValues(String tag, Class<T> cls) { Collection<T> vals = new ArrayList<>(); getClauses(tag).forEach(c -> vals.add(c.getValue(cls))); return vals; } /** * @param tag the tag * @return the tag xrefs for tg */ public Collection<Xref> getTagXrefs(String tag) { Collection<Xref> xrefs = new ArrayList<>(); Clause clause = getClause(tag); if (clause != null) { for (Object ob : clause.getValues()) { if (ob instanceof Xref) { xrefs.add((Xref) ob); } } } return xrefs; } /** * @return the tags */ public Set<String> getTags() { Set<String> tags = new HashSet<>(); getClauses().forEach(cl -> tags.add(cl.getTag())); return tags; } private boolean sameID(Frame f) { if (id == null) { return f.getId() == null; } return verifyNotNull(id).equals(f.getId()); } private boolean sameType(Frame f) { if (type == null) { return f.getType() == null; } return verifyNotNull(type).equals(f.getType()); } /** * @param extFrame the external frame * @throws FrameMergeException the frame merge exception */ public void merge(Frame extFrame) throws FrameMergeException { if (this == extFrame) { return; } if (!sameID(extFrame)) { throw new FrameMergeException("ids do not match"); } if (!sameType(extFrame)) { throw new FrameMergeException("frame types do not match"); } extFrame.getClauses().forEach(this::addClause); // note we do not perform a document structure check at this point } /** * Check this frame for violations, i.e. cardinality constraint violations. * * @throws FrameStructureException the frame structure exception * @see OboInOwlCardinalityTools for equivalent checks in OWL */ public void check() { if (FrameType.HEADER.equals(type)) { checkMaxOneCardinality(OboFormatTag.TAG_ONTOLOGY, OboFormatTag.TAG_FORMAT_VERSION, OboFormatTag.TAG_DATE, OboFormatTag.TAG_DEFAULT_NAMESPACE, OboFormatTag.TAG_SAVED_BY, OboFormatTag.TAG_AUTO_GENERATED_BY); } if (FrameType.TYPEDEF.equals(type)) { checkMaxOneCardinality(OboFormatTag.TAG_DOMAIN, OboFormatTag.TAG_RANGE, OboFormatTag.TAG_IS_METADATA_TAG, OboFormatTag.TAG_IS_CLASS_LEVEL_TAG); } if (!FrameType.HEADER.equals(getType())) { List<Clause> tagIdClauses = getClauses(OboFormatTag.TAG_ID); if (tagIdClauses.size() != 1) { throw new FrameStructureException(this, "cardinality of id field must be 1"); } // this call will verify that the value is not null tagIdClauses.get(0).getValue(); if (getId() == null) { throw new FrameStructureException(this, "id field must be set"); } } Collection<Clause> iClauses = getClauses(OboFormatTag.TAG_INTERSECTION_OF); if (iClauses.size() == 1) { throw new FrameStructureException(this, "single intersection_of tags are not allowed"); } checkMaxOneCardinality(OboFormatTag.TAG_IS_ANONYMOUS, OboFormatTag.TAG_NAME, // OboFormatTag.TAG_NAMESPACE, OboFormatTag.TAG_DEF, OboFormatTag.TAG_COMMENT, OboFormatTag.TAG_IS_ANTI_SYMMETRIC, OboFormatTag.TAG_IS_CYCLIC, OboFormatTag.TAG_IS_REFLEXIVE, OboFormatTag.TAG_IS_SYMMETRIC, OboFormatTag.TAG_IS_TRANSITIVE, OboFormatTag.TAG_IS_FUNCTIONAL, OboFormatTag.TAG_IS_INVERSE_FUNCTIONAL, OboFormatTag.TAG_IS_OBSELETE, OboFormatTag.TAG_CREATED_BY, OboFormatTag.TAG_CREATION_DATE); } /** * Check max one cardinality. * * @param tags the tags * @throws FrameStructureException frame structure exception */ private void checkMaxOneCardinality(OboFormatTag... tags) { for (OboFormatTag tag : tags) { if (getClauses(tag).size() > 1) { throw new FrameStructureException(this, "multiple " + tag.getTag() + " tags not allowed."); } } } /** The Enum FrameType. */ public enum FrameType { //@formatter:off /** HEADER. */ HEADER, /** TERM. */ TERM, /** TYPEDEF. */ TYPEDEF, /** INSTANCE. */ INSTANCE, /** ANNOTATION. */ ANNOTATION //@formatter:on } }