/* * ome.dsl.SemanticType * * Copyright 2006-2014 University of Dundee. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.dsl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.regex.Pattern; /** * represents a SemanticType <b>definition</b>. * * @author <br> * Josh Moore      <a * href="mailto:josh.moore@gmx.de"> josh.moore@gmx.de</a> * @version 3.0 * @since OMERO-3.0 */ public abstract class SemanticType { // Patterns for reducing name lengths final static private Pattern annPattern = Pattern.compile("annotation"); final static private Pattern cntPattern = Pattern.compile("FK_count_to"); final static private Pattern grpPattern = Pattern.compile("experimentergroup"); final static private Pattern acqPattern = Pattern.compile("screenacquisition"); final static private String VM_QUOTE = "\\\""; public final static Set<String> RESTRICTED_COLUMNS = Collections .unmodifiableSet(new HashSet<String>(Arrays.asList("column", "constant", "file", "group", "mode", "power", "ref", "reverse", "rows", "row", "session", "size"))); public final static Set<String> RESTRICTED_TABLE = Collections .unmodifiableSet(new HashSet<String>(Arrays.asList("session", "share"))); // TYPE identifiers public final static String ABSTRACT = "abstract"; public final static String TYPE = "type"; public final static String CONTAINER = "container"; public final static String RESULT = "result"; public final static String LINK = "link"; public final static String ENUM = "enum"; public final static Set<String> TYPES = new HashSet<String>(); static { TYPES.add(ABSTRACT); TYPES.add(TYPE); TYPES.add(CONTAINER); TYPES.add(RESULT); TYPES.add(LINK); TYPES.add(ENUM); } public final static Map<String, Class<?>> TYPES2CLASSES = new HashMap<String, Class<?>>(); static { TYPES2CLASSES.put(ABSTRACT, AbstractType.class); TYPES2CLASSES.put(TYPE, BaseType.class); TYPES2CLASSES.put(CONTAINER, ContainerType.class); TYPES2CLASSES.put(LINK, LinkType.class); TYPES2CLASSES.put(RESULT, ResultType.class); TYPES2CLASSES.put(ENUM, EnumType.class); } /** * Database profile, i.e. ${omero.db.profile}. * * @see <a href="https://trac.openmicroscopy.org/ome/ticket/73">ticket 73</a> */ public final String profile; // all properties private List<Property> properties = new ArrayList<Property>(); /** future class name; required for all types */ private String id; private String table; /** optional item */ private SemanticType superclass; private String discriminator; // possible interfaces private Boolean abstrakt; private Boolean annotated; private Boolean described; private Boolean global; private Boolean immutable; private Boolean named; private final Set<String> uniqueConstraints = new HashSet<String>(); /** * sets the the various properties available in attrs USING DEFAULTS IF NOT * AVAILABLE. Subclasses may override these values. */ public SemanticType(String profile, Properties attrs) { this.profile = profile; setId(attrs.getProperty("id", getId())); setTable(typeToColumn(getId())); if (null == getId()) { throw new IllegalStateException("All types must have an id"); } setSuperclass(attrs.getProperty("superclass")); setDiscriminator(attrs.getProperty("discriminator")); setAnnotated(Boolean.valueOf(attrs.getProperty("annotated", "false"))); setAbstract(Boolean.valueOf(attrs.getProperty("abstract", "false"))); setDescribed(Boolean.valueOf(attrs.getProperty("described", "false"))); setGlobal(Boolean.valueOf(attrs.getProperty("global", "false"))); setImmutable(Boolean.valueOf(attrs.getProperty("immutable", "false"))); setNamed(Boolean.valueOf(attrs.getProperty("named", "false"))); // TODO add "UnsupportedOperation for any other properties in attrs. // same in Property } /** * A database is "restrictive" if it prevents the use of certain columns * and table names, both keywords and lengths. */ public boolean isRestrictive() { return ! profile.equals("psql"); } public void validate() { // Left empty in-case anyone forgets to override. } /** * creates a new type based on the element-valued key in TYPES2CLASSES. Used * mainly by the xml reader */ public static SemanticType makeNew(String profile, String element, Properties attributes) throws IllegalArgumentException, IllegalStateException { Class<?> klass = TYPES2CLASSES.get(element); if (null == klass) { throw new IllegalArgumentException( "TYPES2CLASSES does not contain type " + element); } SemanticType st; try { st = (SemanticType) klass.getConstructor( new Class[] { String.class, Properties.class }).newInstance( new Object[] { profile, attributes }); } catch (Exception e) { throw new IllegalStateException( "Cannot instantiate class " + klass, e); } return st; } @Override public String toString() { String result = "\n" + getId(); for (Iterator<Property> it = getProperties().iterator(); it.hasNext();) { Property element = it.next(); result += "\n " + element.toString(); } return result; } public static String typeToColumn(String type) { return type.substring(type.lastIndexOf(".") + 1).toLowerCase(); } // // Getters and Setters // public void setAbstract(Boolean abstrakt) { this.abstrakt = abstrakt; } public Boolean getAbstract() { return abstrakt; } public void setAnnotated(Boolean annotated) { this.annotated = annotated; } public Boolean getAnnotated() { return annotated; } public void setId(String id) { this.id = id; } public String getId() { return id; } public int getLastDotInId() { int idx = id.lastIndexOf("."); if (idx < 0) { throw new RuntimeException(id + " doesn't have a package. " + "Use of default package prohibited."); } return idx; } /** * Read-only property */ public String getPackage() { return id.substring(0, getLastDotInId()); } /** * Read-only property */ public String getShortname() { return id.substring(getLastDotInId() + 1, id.length()); } /** * Read-only property. Introduced during ticket:73 in order to handle * databases with relation name length restrictions. */ public String countName(Property p) { String countName = String.format( "count_%s_%s_by_owner", getShortname(), p.getName()); return reduce(replace(countName)); } /** * Read-only property. Introduced during ticket:73 in order to handle * databases with relation name length restrictions. */ public String indexName(Property p) { String indexName = String.format( "i_%s_%s", getShortname(), p.getName()); return reduce(replace(indexName)); } public String columnName(Property p) { return columnName(p, VM_QUOTE); } public String columnName(Property p, String quote) { String columnName = p.getName(); if (RESTRICTED_COLUMNS.contains(columnName)) { columnName = quote(columnName, quote); } return columnName; } public String tableName() { SemanticType base = this; while (base.superclass != null && base.getDiscriminator() != null) { base = base.superclass; } String tableName = base.getTable(); if (isRestrictive() && RESTRICTED_TABLE.contains(tableName)) { tableName = tableName + "_"; } return reduce(tableName); } public String inverse(Property p) { String inverse = p.getInverse(); if (RESTRICTED_COLUMNS.contains(inverse)) { inverse = quote(inverse); } return inverse; } public String typeAnnotation(Property p) { String typeAnnotation = p.getTypeAnnotation(); typeAnnotation = typeAnnotation.replace("@PROFILE@", profile); if (isRestrictive()) { if (typeAnnotation.contains("TextType")) { typeAnnotation = "@org.hibernate.annotations.ColumnTransformer(read=\"to_char("+ columnName(p) + ")\")"; } else if ("java.lang.String".equals(p.getType()) && ! p.getNullable().booleanValue()) { // See ticket:3884 typeAnnotation = "@org.hibernate.annotations.ColumnTransformer(write=\"coalesce(?, ' ')\")"; } } return typeAnnotation; } public String propName(Property p) { String name = p.getName(); if (RESTRICTED_COLUMNS.contains(name)) { name = quote(name); } return name; } public void setTable(String table) { this.table = table; } public String getTable() { return table; } public String getSequenceName() { // Since in the restrictive databases that we have at the moment // sequences aren't objects but rows in a table, we can keep this // as it is. return getTable(); } public String fk(String fkvalue) { return reduce(replace(fkvalue)); } private String replace(String name) { if (isRestrictive()) { name = annPattern.matcher(name).replaceAll("ann"); name = cntPattern.matcher(name).replaceAll("FK_cnt_"); name = grpPattern.matcher(name).replaceAll("group"); name = acqPattern.matcher(name).replaceAll("scr_acq"); } return name; } private String reduce(String name) { if (isRestrictive()) { if (name.length() > 30) { String keep = name.substring(0, 27); String reduce = name.substring(28); name = keep + reduce.length(); } } return name; } private String quote(String name) { return quote(name, VM_QUOTE); } private String quote(String name, String quote) { name = quote + name + quote; return name; } /* * Here the finalized ST instance is passed back into this object by the * post-processing step for calculating property closures and similar. */ public void setActualSuperClass(SemanticType st) { this.superclass = st; } public SemanticType getActualSuperClass() { return this.superclass; } public void setSuperclass(String superclass) { if (superclass != null) { Properties p = new Properties(); p.setProperty("id", superclass); this.superclass = new SemanticType(profile, p) { }; } } public String getSuperclass() { return superclass == null ? null : superclass.getId(); } public void setDiscriminator(String discriminator) { this.discriminator = discriminator; } public String getDiscriminator() { return discriminator; } public void setNamed(Boolean named) { this.named = named; } public Boolean getNamed() { return named; } public void setDescribed(Boolean described) { this.described = described; } public Boolean getDescribed() { return described; } public void setImmutable(Boolean immutable) { this.immutable = immutable; } public Boolean getImmutable() { return immutable; } public void setProperties(List<Property> properties) { this.properties = properties; } public List<Property> getProperties() { return properties; } public Property getProperty(String name) { for (Property p : getProperties()) { String fn = p.getName(); if (fn.equals(name)) { return p; } } return null; } /** * Read-only method which currently filters out {@link EntryField} */ public List<Property> getClassProperties() { List<Property> rv = new ArrayList<Property>(); for (Property property : getProperties()) { if (!(property instanceof EntryField)) { rv.add(property); } } return rv; } /** * Read-only method which returns a set of this class's * {@link #getClassProperties()} as well as those of the entire inheritance * hierarchy. */ public List<Property> getPropertyClosure() { List<Property> rv = getClassProperties(); if (superclass != null) { rv.addAll(superclass.getPropertyClosure()); } return rv; } public List<Property> getRequiredSingleProperties() { List<Property> rv = new ArrayList<Property>(); for (Property property : getClassProperties()) { boolean req = property.getRequired() == null ? false : property .getRequired().booleanValue(); boolean col = property.getOne2Many() == null ? false : property .getOne2Many().booleanValue(); if (req && !col) { rv.add(property); } } return rv; } public String getCheck() { return ""; } public void setGlobal(Boolean global) { this.global = global; } public Boolean getGlobal() { return global; } /** * Read-only field */ public String getDetails() { if (!getGlobal().booleanValue()) { if (!getImmutable().booleanValue()) { return "MutableDetails"; } return "Details"; } return "GlobalDetails"; } /** * Read-only property to be overwritten by subclasses */ public boolean getIsLink() { return false; } public boolean getIsAnnotationLink() { return false; } /** * Read-only property to be overwritten by subclasses */ public boolean getIsEnum() { return false; } public Set<String> getUniqueConstraints() { return uniqueConstraints; } /** * Helper method */ public static String unqualify(String klass) { if (klass == null) { return null; } int idx = klass.lastIndexOf("."); if (idx < 0) { return klass; } else { return klass.substring(idx + 1, klass.length()); } } } // // // Subclasses which implement specific logic. (Essentially aliases for multiple // properties) // // class BaseType extends SemanticType { public BaseType(String profile, Properties attrs) { super(profile, attrs); } } class AbstractType extends SemanticType { public AbstractType(String profile, Properties attrs) { super(profile, attrs); this.setAbstract(Boolean.TRUE); } } class ContainerType extends SemanticType { public ContainerType(String profile, Properties attrs) { super(profile, attrs); // TODO } } class LinkType extends SemanticType { public LinkType(String profile, Properties attrs) { super(profile, attrs); } @Override public boolean getIsLink() { return true; } @Override public boolean getIsAnnotationLink() { Property p = getProperty("child"); String t = p.getType(); if (t.equals("ome.model.annotations.Annotation")) { return true; } return false; } } class ResultType extends SemanticType { public ResultType(String profile, Properties attrs) { super(profile, attrs); // TODO } } class EnumType extends SemanticType { public EnumType(String profile, Properties attrs) { super(profile, attrs); setGlobal(Boolean.TRUE); setImmutable(Boolean.TRUE); Properties props = new Properties(); props.setProperty("name", "value"); props.setProperty("type", "string"); props.setProperty("unique", "true"); RequiredField value = new RequiredField(this, props); getProperties().add(value); } @Override public boolean getIsEnum() { return true; } // TODO: only value? at least value? // public void validate(){ // for (Iterator it = getProperties().iterator(); it.hasNext();) { // if (it.next().getClass()!=EntryField.class){ // throw new IllegalStateException("EnumTypes can only contain // EntryProperties."); // } // } // super.validate(); // } }