package fr.imag.adele.apam.util; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import fr.imag.adele.apam.AttrType; import fr.imag.adele.apam.CST; import fr.imag.adele.apam.Component; import fr.imag.adele.apam.Instance; import fr.imag.adele.apam.RelationDefinition; import fr.imag.adele.apam.impl.ComponentImpl; import fr.imag.adele.apam.impl.InstanceImpl; /* * * Syntax of substitution is : subst == [prefix "+"][sourceName]["." depId] "$" attr ["+" Suffix] * */ public class Substitute { public static class SplitSub { public String attr; public String sourceName; public List<String> depIds; public String prefix; public String suffix; /** * Stores the differents elements, and separate the prefix and suffix * from source and attr * * Syntax is : subst == [prefix "+"][sourceName]["." depId] "$" attr * ["+" Suffix] * * @param attrSub * : cannot be null * @param sourceName * can be null * @param depId * can be null */ public SplitSub(String attrSub, String sourceName, String depId) { // separate the prefix and suffix from source and attr int s = attrSub.indexOf('+'); if (s == 0) { // invalid : attrsub = "+azer" attr = ""; suffix = attrSub.substring(1); } if (s > 0) { attr = attrSub.substring(0, s); suffix = attrSub.substring(s + 1); } else { this.attr = attrSub; } // Compute the navigation if (depId == null || depId.isEmpty()) { this.depIds = null; } else { depIds = new ArrayList<String>(); while (depId != null) { s = depId.indexOf('.'); if (s < 0) { // It is the last one depIds.add(depId); depId = null; } else { depIds.add(depId.substring(0, s)); depId = depId.substring(s + 1); } } } s = sourceName.indexOf('+'); if (s < 1) { // invalid : source = "+azer" or "azert" prefix = null; } else { prefix = sourceName.substring(0, s); } this.sourceName = sourceName.substring(s + 1); if (this.sourceName.isEmpty()) { this.sourceName = "this"; } } @Override public String toString() { StringBuffer ret = new StringBuffer(""); if (prefix != null) { ret.append(prefix + "+"); } if (sourceName != null) { ret.append(sourceName); } if (depIds != null) { for (String depId : depIds) { ret.append("." + depId); } } ret.append("$" + attr); if (suffix != null) { ret.append("+" + suffix); } return ret.toString(); } } // static Substitute s = new Substitute () ; private final static Logger logger = LoggerFactory.getLogger(Substitute.class); /** * Return the value of attribute "attr" of component source, if the types * are compatibles. Types are compatible if the type of "attr" and * "typeAttr" are equal. * * @param source * @param attr * @param sourceTypeAttr * @return */ @SuppressWarnings("unchecked") private static Object checkReturnSub(Component source, SplitSub sub, String sourceAttr, AttrType sourceTypeAttr) { AttrType t = source.getAttrType(sub.attr); if (t == null) { return null; } if (!checkSubType(sourceTypeAttr, t, sourceAttr, sub)) { return null; } Object ret = source.getPropertyObject(sub.attr); if (ret == null) { return null; } if (t.type == AttrType.INTEGER) { return ret; } if (t.isSet) { Set<String> retCol = new HashSet<String>(); for (String s : (Set<String>) ret) { retCol.add(concatSub(sub, s)); } return retCol; } if (ret instanceof String) { return concatSub(sub, (String) ret); } return ret; } /** * * @param sourceType * @param targetType * @param attr * @param sub * @return */ public static boolean checkSubType(AttrType sourceType, AttrType targetType, String attr, SplitSub sub) { /* * We are in a filter, source is the target, and the real source is * ignored. Validity has been checked at compile time */ if (sourceType == null) { return true; } if (targetType == null) { return false; } if (sub.prefix != null || sub.suffix != null) { // The result is a // string, not an // enumeration if (sourceType.type == AttrType.STRING) { return true; } logger.error("Attribute " + attr + " of type " + sourceType.typeString + " : invalid substitution with string \"" + sub + "\". Attribute " + sub.attr + " of type : " + targetType.typeString); return false; } if (sourceType.type != targetType.type) { logger.error("Attribute " + attr + " of type " + sourceType.typeString + " : invalid substitution with \"" + sub + "\". Attribute " + sub.attr + " of type : " + targetType.typeString); return false; } if (sourceType.type == AttrType.ENUM && !sourceType.enumValues.containsAll(targetType.enumValues)) { logger.error("Attribute " + attr + " of type " + sourceType.typeString + " : Not the same enumeration set as attribute " + sub.attr + " of type : " + targetType.typeString); return false; } return (sourceType.isSet || !targetType.isSet); } private static String concatSub(SplitSub sub, String val) { String ret = val; if (sub.prefix != null) { ret = sub.prefix + ret; } if (sub.suffix != null) { ret = ret + sub.suffix; } return ret; } public static Object functionSubstitute(String attr, String value, Component source) { if (!(source instanceof Instance)) { logger.error("Invalid function substitution. " + source.getName() + " is not an instance."); } String func = value.substring(1); Class<?> c = ((InstanceImpl) source).getServiceObject().getClass(); try { Method[] allMethods = c.getDeclaredMethods(); for (Method m : allMethods) { if (m.getName().equals(func)) { Object ret = m.invoke(((InstanceImpl) source).getServiceObject(), (Instance) source); return ret; } } logger.error("Not found method " + func + " in class " + c.getCanonicalName() + " of instance " + source); return null; } catch (Exception e) { logger.error("Invalid invoke on " + func + " in class " + c.getCanonicalName() + " of instance " + source); e.printStackTrace(); } return null; } public static boolean isSubstitution(Object value) { return ((value instanceof String) && !((String) value).trim().isEmpty() && (((String) value).trim().charAt(0) == '$' || ((String) value).trim().charAt(0) == '@' || ((String) value).trim().charAt(0) == '\\')); } /** * From an unique component source, and a list of relation names, compute * the set of components reached by that navigation. * * @param firstSource * @param navigation * @return */ private static Set<Component> navigate(Component firstSource, List<String> navigation) { // Compute the navigation. It returns a set of components // RelToResolve depDcl; Set<Component> dests = new HashSet<Component>(); Set<Component> sources = new HashSet<Component>(); sources.add(firstSource); if (navigation == null || navigation.isEmpty()) { return sources; } Set<Component> tempDests; for (String depId : navigation) { for (Component source : sources) { RelationDefinition depDcl = source.getRelation(depId); if (depDcl == null && !CST.isFinalRelation(depId)) { logger.error("relation " + depId + " undefined for component " + source.getName()); } else { tempDests = ((ComponentImpl) source).getRawLinkDests(depId); if (tempDests.isEmpty()) { logger.debug("relation id " + depId + " not resolved for component " + source.getName()); } else { dests.addAll(tempDests); // never null } } } if (dests.isEmpty()) { return dests; // no solution } // Go one step further sources.clear(); sources.addAll(dests); dests.clear(); } return sources; } /** * Value is a substitution, with syntax $[source[.depIds]]"$"Attr Only split * in three parts "source", "depIds" and "attr" It escapes the char '.' if * preceded by '\' * * @param value * a string to substitute * @return a SplitSub object the three elements. Null is not a substitution */ public static SplitSub split(String value) { if (value.charAt(0) != '$') { return null; } // eliminate first "$" value = value.substring(1); // If no "$Attr" it is invalid int i = value.indexOf('$'); if (i == -1) { return null; } String attr = value.substring(i + 1); if (i == 0) { // no source, no navigation "$$Attr" return new SplitSub(attr, "this", null); } String prefix = value.substring(0, i); // if prefix contain '\.' must escape the '.' boolean finished = false; int k = 0; int j = -1; j = prefix.indexOf('.', k); while (!finished) { k = prefix.indexOf("\\.", k); if (k >= 0) { prefix = prefix.substring(0, k) + prefix.substring(k + 1); k++; if (j == k) { j = prefix.indexOf('.', k); } } else { finished = true; } } if (j == -1) { return new SplitSub(attr, prefix, null); } if (j == 0) { return new SplitSub(attr, "this", prefix.substring(1)); } return new SplitSub(attr, prefix.substring(0, j), prefix.substring(j + 1)); } /** * Provided that component sources has an attribute "attr=value", with value * a meta-substitution, returns the value after the substitution. * * If not a substitution returns the value as is. If the substitution fails, * returns null. * * Syntax is : subst == [prefix "+"][sourceName][{"." depId}] "$" attr ["+" * Suffix] * * The depId element is valid only if source is an instance. * * @param attr * @param value * @param source * the component that has the "attr=value" property * @return */ @SuppressWarnings("unchecked") public static Object substitute(String attr, Object valueObject, Component source) { /* * No substitution cases */ if (source == null || (valueObject == null) || (!(valueObject instanceof String))) { return valueObject; } String value = ((String) valueObject).trim(); if (value.startsWith("\\$") || value.startsWith("\\@")) { return value.substring(1); } if (value.isEmpty() || (value.charAt(0) != '$' && value.charAt(0) != '@')) { return valueObject; } /* * Substitution is needed */ // A function to call if (value.charAt(0) == '@') { return functionSubstitute(attr, value, source); } /* * It is a meta substitution */ AttrType st = null; // If attr is null, it is because it is a substitution in a filter. // Source is currently the target ! Do no check the attr if (attr != null) { st = source.getAttrType(attr); } SplitSub sub = split(value); if (sub == null) { return value; } if (!sub.sourceName.equals("this")) { // Look for the source component // source = CST.apamResolver.findComponentByName(source, // sub.sourceName); // Look for existing component only (no resolution). Component newSource = CST.componentBroker.getComponent(sub.sourceName); if (newSource == null) { logger.error("Component " + sub.sourceName + " not found in substitution : " + value + " of attribute " + attr); return null; } if (!Visible.isVisible(source, newSource)) { logger.error("Component " + sub.sourceName + " is not visible from " + source + " in substitution : " + value + " of attribute " + attr); return null; } source = newSource; } if (sub.depIds == null) { return checkReturnSub(source, sub, attr, st); } /* * look for the navigation from the source component */ Set<Component> dest = navigate(source, sub.depIds); if (dest.size() == 1) { return checkReturnSub(dest.iterator().next(), sub, attr, st); } /* * We have more than one source; TypeAttr must be a set, and each * destination must be either a singleton of the right type, or the same * type of Set. We are building a collection with all the values. */ if (!st.isSet) { logger.error("Invalid type for attribute " + attr + " It must be a set for a multiple relation or navigation" + sub.depIds); return null; } if (st.type == AttrType.INTEGER) { Set<Integer> retSetInt = new HashSet<Integer>(); for (Component d : dest) { Object oneVal = checkReturnSub(d, sub, attr, st); if (oneVal != null) { if (oneVal instanceof Set) { retSetInt.addAll((Set<Integer>) oneVal); } else { retSetInt.add((Integer) oneVal); } } } return retSetInt; } Set<String> retSetString = new HashSet<String>(); for (Component d : dest) { Object oneVal = checkReturnSub(d, sub, attr, st); if (oneVal != null) { if (oneVal instanceof Set) { retSetString.addAll((Set<String>) oneVal); } else { retSetString.add((String) oneVal); } } } return retSetString; } /** * Transforms a list of constraint in string, into a list of filters, and * substitute the values if needed * * @param filterString * @param component * @return */ public static List<ApamFilter> toFiltersSubst(List<String> filterString, Component component) { if (component == null) { return null; } List<ApamFilter> ret = new ArrayList<ApamFilter>(); if (filterString == null || filterString.isEmpty()) { return ret; } for (String sf : filterString) { try { ret.add(ApamFilter.newInstanceApam(sf, component)); } catch (Exception e) { logger.error("Invalid filter " + sf + " for component " + component.getName()); } } return ret; } /** * Transforms a list of constraints in string, into a list of filters, and * substitute the values if needed * * @param filterString * @param component * @return */ public static Set<ApamFilter> toFiltersSubst(Set<String> filterString, Component component) { if (component == null) { return null; } Set<ApamFilter> ret = new HashSet<ApamFilter>(); for (String sf : filterString) { try { ret.add(ApamFilter.newInstanceApam(sf, component)); } catch (Exception e) { logger.error("Invalid filter " + sf + " for component " + component.getName()); } } return ret; } }