/** ExtractClass extracts fields into a new nested class. * If fields were initialised, their value is passed to * the constructor of the new class. */ aspect ExtractClass { public void ClassDecl.doExtractClass(Collection fds, String newClassName, String newFieldName, boolean encapsulate, boolean toplevel) { extractClass(fds, newClassName, newFieldName, encapsulate, toplevel); programRoot().eliminate(LOCKED_NAMES, LOCKED_DATAFLOW); } public void ClassDecl.extractClass(Collection fds_coll, String newClassName, String newFieldName, boolean encapsulate, boolean toplevel) { // fds_coll Collection -> List, we need order java.util.List fds = new ArrayList(fds_coll); Collections.sort(fds, new Comparator() { //@Override public int compare(FieldDeclaration o1, FieldDeclaration o2) { return o1.getChildIndex() - o2.getChildIndex(); } }); if(!isFieldsInIncreasingIndexOrder(fds)) throw new RefactoringException("fields are expected to be in increasing index order"); // create struct int maxvis = maxVisibility(fds); Modifiers struct_mods = new Modifiers("static"); struct_mods.setVisibility(toplevel ? VIS_PUBLIC : maxvis); ClassDecl struct = new ClassDecl(struct_mods, newClassName, new Opt(), new List(), new List()); // insert field int first_index = fds.iterator().next().getChildIndex(); FieldDeclaration wrapperField = createNewMemberField(maxvis, struct, newFieldName, first_index); MemberTypeDecl mtd = insertUnusedType(struct, wrapperField.getChildIndex()); programRoot().flushCaches(); // move fields into the struct and leave initializers int i = 0; java.util.Map inits = new LinkedHashMap(); for (FieldDeclaration fd : fds) { if (fd.getModifiers().isStatic()) throw new RefactoringException("cannot extract field "+fd.name()); InstanceInitializer init = wrapField(fd, wrapperField, struct, i++); if (init != null) inits.put(fd, init); } programRoot().flushCaches(); programRoot().eliminate(LOCKED_NAMES); // try to move initializers one by one after wrapper field i = 0; for (InstanceInitializer init : inits.values()) { int old_idx = init.getChildIndex(); ASTUndoCheckPoint cp = Program.createASTUndoCheckPoint(); try { init.lockDataFlow(); init.lockAllNames(); this.removeBodyDecl(init); this.getBodyDeclList().insertChild(init, wrapperField.getChildIndex() + i + 1); flushCaches(); init.unlockDataFlow(); init.eliminateLockedNames(); } catch (RefactoringException re) { // no luck // move init back and break // this.removeBodyDecl(init); // this.getBodyDeclList().insertChild(init, old_idx); // flushCaches(); // init.unlockDataFlow(); // init.eliminateLockedNames(); programRoot().undoUntil(cp); break; } i++; hostType().flushCaches(); } if (encapsulate) { struct.createInitContructor(); } if (inits.size() > 0 && allInitializersAreInOrderAfterWrapperField(inits, wrapperField) && !initializerEvaluationOrderMatters(inits, wrapperField)) { // modify contructor and calls if (!encapsulate) { struct.createInitContructor(); } for (InstanceInitializer init : inits.values()) { init.lockDataFlow(); init.lockAllNames(); } // pass init expr in constructor, and default values for others for (FieldDeclaration fd : fds) { // get init or pass default value Expr init_exp = null; if (inits.get(fd) != null) { init_exp = ((AssignSimpleExpr)((ExprStmt)inits.get(fd).getBlock().getStmt(0)).getExpr()).getSource(); this.removeBodyDecl(inits.get(fd)); } else if (fd.type() instanceof BooleanType) init_exp = new BooleanLiteral(false); else if (fd.type() instanceof CharType) init_exp = new CharacterLiteral(new Character((char)0).toString()); else if (fd.type() instanceof NumericType) init_exp = new IntegerLiteral(0); else if (fd.type() instanceof ReferenceType) init_exp = new NullLiteral("null"); else throw new RefactoringException("type default value not accounted for"); ((ClassInstanceExpr)wrapperField.getInit()).getArgList().add(init_exp); } flushCaches(); unlockDataFlow(); eliminateLockedNames(); } else { // try to merge initializer blocks to minimize clutter InstanceInitializer last = null; for (InstanceInitializer init : inits.values()) { if (last == null) { last = init; continue; } if (last.getChildIndex() == init.getChildIndex() - 1) { // merge last.getBlock().addStmt(init.getBlock().getStmt(0)); this.removeBodyDecl(init); } else { last = init; } } } // struct settings if(toplevel) { for(FieldDeclaration fd : fds) fd.getModifiers().setVisibility(VIS_PUBLIC); mtd.moveToToplevel(true, null, true); } if(encapsulate) for(FieldDeclaration fd : fds) fd.selfEncapsulate(); } private void ClassDecl.createInitContructor() { Modifiers m = new Modifiers(); m.setVisibility(getVisibility()); ConstructorDecl cd = new ConstructorDecl((Modifiers) m.fullCopyAndDetach(), name(), new List(), new List(), new Opt(), new Block()); this.addBodyDecl(cd); // ConstructorDecl cd_def = new ConstructorDecl(m.fullCopyAndDetach(), name(), new List(), new List(), new Opt(), new Block()); // this.addBodyDecl(cd_def); for (BodyDecl bd : this.getBodyDecls()) { if (bd instanceof FieldDeclaration && !((FieldDeclaration)bd).isStatic()) { FieldDeclaration fd = (FieldDeclaration)bd; ParameterDeclaration pd = new ParameterDeclaration(new Modifiers(), fd.type().createLockedAccess(), fd.name()); cd.addParameter(pd); cd.getBlock().addStmt(AssignExpr.asStmt(fd.createLockedAccess(), pd.createLockedAccess())); } } eliminateLockedNames(); } private boolean ClassDecl.initializerEvaluationOrderMatters(java.util.Map inits, FieldDeclaration wrapperField) { // if we can invert order of inits then the evaluation order does not matter assert(allInitializersAreInOrderAfterWrapperField(inits, wrapperField)); boolean ret = false; try { for (InstanceInitializer init : inits.values()) { init.lockDataFlow(); } // invert inits order for (InstanceInitializer init : inits.values()) { this.removeBodyDecl(init); this.getBodyDeclList().insertChild(init, wrapperField.getChildIndex() + 1); } flushCaches(); /*for (Pair fdii : inits) { InstanceInitializer init = fdii.snd(); init.unlockDataFlow();; init.eliminateLockedNames(); }*/ // as unlocking of names, unfortunately, tends to replace nodes in AST we unlock _all_ the dataflow first unlockDataFlow(); } catch (RefactoringException e) { ret = true; } finally { // restore inits order int i = 0; for (InstanceInitializer init : inits.values()) { this.removeBodyDecl(init); this.getBodyDeclList().insertChild(init, wrapperField.getChildIndex() + i + 1); i++; } flushCaches(); /*for (Pair fdii : inits) { InstanceInitializer init = fdii.snd(); init.unlockDataFlow(); init.eliminateLockedNames(); }*/ // as unlocking of names, unfortunately, tends to replace nodes in AST we unlock _all_ the dataflow first unlockDataFlow(); } return ret; } private boolean ClassDecl.allInitializersAreInOrderAfterWrapperField(java.util.Map inits, FieldDeclaration wrapperField) { int prev = wrapperField.getChildIndex(); for (InstanceInitializer init : inits.values()) { if (!(init.getChildIndex() == prev + 1)) { return false; } prev++; } return true; } private boolean ClassDecl.isFieldsInIncreasingIndexOrder(java.util.List fds) { int last = 0; for(FieldDeclaration fd : fds) { if(!(fd.getChildIndex() >= last)) return false; last = fd.getChildIndex(); } return true; } private int ClassDecl.maxVisibility(Collection fds) { int vis = VIS_PRIVATE; for(FieldDeclaration fd : fds) if(fd.getVisibility() > vis) vis = fd.getVisibility(); return vis; } public FieldDeclaration ClassDecl.createNewMemberField(int vis, TypeDecl type, String fieldName, int idx) { programRoot().lockNames(fieldName); Expr expr = new ClassInstanceExpr(type.createLockedAccess(), new List(), new Opt()); FieldDeclaration fd = new FieldDeclaration(new Modifiers(vis), type.createLockedAccess(), fieldName, expr); this.getBodyDeclList().insertChild(fd, idx); return fd; } // assumes wrapperField is initialised in the program tree with an instance of wrapperClass public InstanceInitializer ClassDecl.wrapField(FieldDeclaration toMove, FieldDeclaration wrapperField, ClassDecl struct, int idx) { // replace all uses to proxy uses for(VarAccess va : toMove.uses()) { VarAccess q = wrapperField.createLockedAccess(); va.replaceWith(q.qualifiesAccess((VarAccess) va.fullCopyAndDetach())); } InstanceInitializer init = null; // move field to struct and leave the initializer if(toMove.hasInit()) { init = new InstanceInitializer(new Block(new ExprStmt( new AssignSimpleExpr( wrapperField.createLockedAccess().qualifiesAccess(toMove.createLockedAccess()), toMove.getInit().wrapArrayInit())))); toMove.setInitOpt(new Opt()); toMove.hostType().getBodyDeclList().insertChild(init, toMove.getChildIndex()); } toMove.hostType().removeBodyDecl(toMove); struct.getBodyDeclList().insertChild(toMove, idx); return init; } }