/* MIT License Copyright (c) 2018-2019 Gang ZHANG Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package depends.entity; import depends.entity.repo.EntityRepo; import depends.relations.IBindingResolver; import depends.relations.Relation; import multilang.depends.util.file.TemporaryFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.lang.ref.WeakReference; import java.util.*; /** * ContainerEntity for example file, class, method, etc. they could contain * vars, functions, ecpressions, type parameters, etc. */ public abstract class ContainerEntity extends DecoratedEntity { private static final Logger logger = LoggerFactory.getLogger(ContainerEntity.class); private ArrayList vars; private ArrayList functions; WeakReference> expressionWeakReference; private ArrayList expressionList; private int expressionCount = 0; private Collection mixins; private Collection resolvedMixins; private ArrayList vars() { if (vars==null) vars = new ArrayList<>(); return this.vars; } private Collection mixins() { if (mixins==null) mixins = new ArrayList<>(); return this.mixins; } private ArrayList functions() { if (functions==null) functions = new ArrayList<>(); return this.functions; } public ContainerEntity() { } public ContainerEntity(GenericName rawName, Entity parent, Integer id) { super(rawName, parent, id); } public void addVar(VarEntity var) { if (logger.isDebugEnabled()) { logger.debug("var found: " + var.getRawName() + ":" + var.getRawType()); } this.vars().add(var); } public ArrayList getVars() { if (vars==null) return new ArrayList<>(); return this.vars(); } public void addFunction(FunctionEntity functionEntity) { this.functions().add(functionEntity); } public ArrayList getFunctions() { if (functions==null) return new ArrayList<>(); return this.functions; } public HashMap expressions() { if (expressionWeakReference==null) expressionWeakReference= new WeakReference>(new HashMap<>()); HashMap r = expressionWeakReference.get(); if (r==null) return new HashMap<>(); return r; } public void addExpression(Object key, Expression expression) { expressions().put(key, expression); expressionList().add(expression); expressionCount = expressionList.size(); } public boolean containsExpression(Object key) { return expressions().containsKey(key); } /** * For all data in the class, infer their types. Should be override in * sub-classes */ public void inferLocalLevelEntities(IBindingResolver bindingResolver) { super.inferLocalLevelEntities(bindingResolver); for (VarEntity var : this.vars()) { if (var.getParent()!=this) { var.inferLocalLevelEntities(bindingResolver); } } for (FunctionEntity func : this.getFunctions()) { if (func.getParent()!=this) { func.inferLocalLevelEntities(bindingResolver); } } if (bindingResolver.isEagerExpressionResolve()) { reloadExpression(bindingResolver.getRepo()); resolveExpressions(bindingResolver); cacheExpressions(); } resolvedMixins = identiferToContainerEntity(bindingResolver, getMixins()); } private Collection getMixins() { if (mixins==null) return new ArrayList<>(); return mixins; } private Collection identiferToContainerEntity(IBindingResolver bindingResolver, Collection identifiers) { if (identifiers.size()==0) return null; ArrayList r = new ArrayList<>(); for (GenericName identifier : identifiers) { Entity entity = bindingResolver.resolveName(this, identifier, true); if (entity == null) { continue; } if (entity instanceof ContainerEntity) r.add((ContainerEntity) entity); } return r; } /** * Resolve all expression's type * * @param bindingResolver */ public void resolveExpressions(IBindingResolver bindingResolver) { if (this instanceof FunctionEntity) { ((FunctionEntity)this).linkReturnToLastExpression(); } if (expressionList==null) return; if(expressionList.size()>10000) return; for (Expression expression : expressionList) { // 1. if expression's type existed, break; if (expression.getType() != null) continue; if (expression.isDot()) { // wait for previous continue; } if (expression.getRawType() == null && expression.getIdentifier() == null) continue; // 2. if expression's rawType existed, directly infer type by rawType // if expression's rawType does not existed, infer type based on identifiers if (expression.getRawType() != null) { expression.setType(bindingResolver.inferTypeFromName(this, expression.getRawType()), null, bindingResolver); if (expression.getType() != null) { continue; } } if (expression.getIdentifier() != null) { Entity entity = bindingResolver.resolveName(this, expression.getIdentifier(), true); String composedName = expression.getIdentifier().toString(); Expression theExpr = expression; if (entity==null) { while(theExpr.getParent()!=null && theExpr.getParent().isDot()) { theExpr = theExpr.getParent(); if (theExpr.getIdentifier()==null) break; composedName = composedName + "." + theExpr.getIdentifier().toString(); entity = bindingResolver.resolveName(this, GenericName.build(composedName), true); if (entity!=null) break; } } if (entity != null) { expression.setType(entity.getType(), entity, bindingResolver); continue; } if (expression.isCall()) { List funcs = this.lookupFunctionInVisibleScope(expression.getIdentifier()); if (funcs != null) { for (Entity func:funcs) { expression.setType(func.getType(), func, bindingResolver); } } } else { Entity varEntity = this.lookupVarInVisibleScope(expression.getIdentifier()); if (varEntity != null) { expression.setType(varEntity.getType(), varEntity, bindingResolver); } } } } } public void cacheChildExpressions() { cacheExpressions(); for (Entity child:getChildren()) { if (child instanceof ContainerEntity) { ((ContainerEntity)child).cacheChildExpressions(); } } } public void cacheExpressions() { if (expressionWeakReference==null) return; if (expressionList==null) return; this.expressions().clear(); this.expressionWeakReference.clear(); cacheExpressionListToFile(); this.expressionList.clear(); this.expressionList=null; this.expressionList = new ArrayList<>(); } public void clearExpressions() { if (expressionWeakReference==null) return; if (expressionList==null) return; this.expressions().clear(); this.expressionWeakReference.clear(); this.expressionList.clear(); this.expressionList=null; this.expressionList = new ArrayList<>(); this.expressionUseList = null; } private void cacheExpressionListToFile() { if (expressionCount ==0) return; try { FileOutputStream fileOut = new FileOutputStream(TemporaryFile.getInstance().exprPath(this.id)); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(this.expressionList); out.close(); fileOut.close(); } catch (IOException e) { e.printStackTrace(); } } @SuppressWarnings("unchecked") public void reloadExpression(EntityRepo repo) { if (expressionCount ==0) return; try { FileInputStream fileIn = new FileInputStream(TemporaryFile.getInstance().exprPath(this.id)); ObjectInputStream in = new ObjectInputStream(fileIn); expressionList = (ArrayList) in.readObject(); if (expressionList==null) expressionList = new ArrayList<>(); for (Expression expr:expressionList) { expr.reload(repo,expressionList); } in.close(); fileIn.close(); }catch(IOException | ClassNotFoundException i) { return; } } public List expressionList() { if (expressionList==null) expressionList = new ArrayList<>(); return expressionList; } public boolean containsExpression() { return expressions().size() > 0; } /** * The entry point of lookup functions. It will treat multi-declare entities and * normal entity differently. - for multiDeclare entity, it means to lookup all * entities - for normal entity, it means to lookup entities from current scope * still root * * @param functionName * @return */ public List lookupFunctionInVisibleScope(GenericName functionName) { List functions = new ArrayList<>(); if (this.getMutliDeclare() != null) { for (Entity fromEntity : this.getMutliDeclare().getEntities()) { Entity f = lookupFunctionBottomUpTillTopContainer(functionName, fromEntity); if (f != null) { functions.add(f); return functions; } } } else { ContainerEntity fromEntity = this; Entity f = lookupFunctionBottomUpTillTopContainer(functionName, fromEntity); if (f != null) { functions.add(f); return functions; } } return null; } /** * lookup function bottom up till the most outside container * * @param functionName * @param fromEntity * @return */ private Entity lookupFunctionBottomUpTillTopContainer(GenericName functionName, Entity fromEntity) { while (fromEntity != null) { if (fromEntity instanceof ContainerEntity) { FunctionEntity func = ((ContainerEntity) fromEntity).lookupFunctionLocally(functionName); if (func != null) return func; } for (Entity child:this.getChildren()) { if (child instanceof AliasEntity) { if (child.getRawName().equals(functionName)) return child; } } fromEntity = (ContainerEntity) this.getAncestorOfType(ContainerEntity.class); } return null; } /** * lookup function in local entity. It could be override such as the type entity * (it should also lookup the inherit/implemented types * * @param functionName * @return */ public FunctionEntity lookupFunctionLocally(GenericName functionName) { for (FunctionEntity func : getFunctions()) { if (func.getRawName().equals(functionName)) return func; } return null; } /** * The entry point of lookup var. It will treat multi-declare entities and * normal entity differently. - for multiDeclare entity, it means to lookup all * entities - for normal entity, it means to lookup entities from current scope * still root * * @param varName * @return */ public Entity lookupVarInVisibleScope(GenericName varName) { ContainerEntity fromEntity = this; return lookupVarBottomUpTillTopContainer(varName, fromEntity); } /** * To found the var. * * @param fromEntity * @param varName * @return */ private Entity lookupVarBottomUpTillTopContainer(GenericName varName, ContainerEntity fromEntity) { while (fromEntity != null) { if (fromEntity instanceof ContainerEntity) { VarEntity var = ((ContainerEntity) fromEntity).lookupVarLocally(varName); if (var != null) return var; } for (Entity child:this.getChildren()) { if (child instanceof AliasEntity) { if (child.getRawName().equals(varName)) return child; } } fromEntity = (ContainerEntity) this.getAncestorOfType(ContainerEntity.class); } return null; } public VarEntity lookupVarLocally(GenericName varName) { for (VarEntity var : getVars()) { if (var.getRawName().equals(varName)) return var; } return null; } public VarEntity lookupVarLocally(String varName) { return this.lookupVarLocally(GenericName.build(varName)); } public void addMixin(GenericName moduleName) { mixins().add(moduleName); } public Collection getResolvedMixins() { if (resolvedMixins==null) return new ArrayList<>(); return resolvedMixins; } HashMap> expressionUseList = null; public void addRelation(Expression expression, Relation relation) { String key = relation.getEntity().qualifiedName+relation.getType(); if (this.expressionUseList==null) expressionUseList = new HashMap<>(); if (expressionUseList.containsKey(key)){ Set expressions = expressionUseList.get(key); for (Expression expr:expressions){ if (linkedExpr(expr,expression)) return; } }else{ expressionUseList.put(key,new HashSet<>()); } expressionUseList.get(key).add(expression); super.addRelation(relation); } private boolean linkedExpr(Expression a, Expression b) { Expression parent = a.getParent(); while(parent!=null){ if (parent==b) return true; parent = parent.getParent(); } parent = b.getParent(); while(parent!=null){ if (parent==a) return true; parent = parent.getParent(); } return false; } }