Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/make/src/classes/build/tools/generatenimbus/PainterGenerator.java
32287 views
/*1* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package build.tools.generatenimbus;2526import java.awt.geom.Point2D;27import java.util.ArrayList;28import java.util.HashMap;29import java.util.LinkedHashMap;30import java.util.List;31import java.util.Map;323334/**35* PainterGenerator - Class for generating Painter class java source from a Canvas36*37* Following in the general theory that is used to generate a Painter file.38*39* Each Painter file represents a Region. So there is one painter file per region. In40* skin.laf we support Icon subregions, which are really just hacked versions of the41* parent region.42*43* In order to generate the most compact and efficient bytecode possible for the44* Painters, we actually perform the generation sequence in two steps. The first45* step is the analysis phase, where we walk through the SynthModel for the region46* and discover commonality among the different states in the region. For example,47* do they have common paths? Do they have common colors? Gradients? Is the painting48* code for the different states identical other than for colors?49*50* We gather this information up. On the second pass, we use this data to determine the51* methods that need to be generated, and the class variables that need to be generated.52* We try to keep the actual bytecode count as small as possible so that we may reduce53* the overall size of the look and feel significantly.54*55* @author Richard Bair56* @author Jasper Potts57*/58public class PainterGenerator {59//a handful of counters, incremented whenever the associated object type is encounted.60//These counters form the basis of the field and method suffixes.61//These are all 1 based, because I felt like it :-)62private int colorCounter = 1;63private int gradientCounter = 1;64private int radialCounter = 1;65private int pathCounter = 1;66private int rectCounter = 1;67private int roundRectCounter = 1;68private int ellipseCounter = 1;6970private int stateTypeCounter = 1;7172//during the first pass, we will construct these maps73private Map<String, String> colors = new HashMap<String, String>();74/**75* Code=>method name.76*/77private Map<String, String> methods = new HashMap<String, String>();7879//these variables hold the generated code80/**81* The source code in this variable will be used to define the various state types82*/83private StringBuilder stateTypeCode = new StringBuilder();84/**85* The source code in this variable will be used to define the switch statement for painting86*/87private StringBuilder switchCode = new StringBuilder();88/**89* The source code in this variable will be used to define the methods for painting each state90*/91private StringBuilder paintingCode = new StringBuilder();92/**93* The source code in this variable will be used to add getExtendedCacheKeys94* implementation if needed.95*/96private StringBuilder getExtendedCacheKeysCode = new StringBuilder();97/**98* The source code in this variable will be used to define the methods for decoding gradients99* and shapes.100*/101private StringBuilder gradientsCode = new StringBuilder();102private StringBuilder colorCode = new StringBuilder();103private StringBuilder shapesCode = new StringBuilder();104/**105* Map of component colors keyed by state constant name106*/107private Map<String, List<ComponentColor>> componentColorsMap =108new LinkedHashMap<String, List<ComponentColor>>();109/**110* For the current state the list of all component colors used by this111* painter, the index in this list is also the index in the runtime array112* of defaults and keys.113*/114private List<ComponentColor> componentColors = null;115116PainterGenerator(UIRegion r) {117generate(r);118}119120private void generate(UIRegion r) {121for (UIState state : r.getBackgroundStates()) {122Canvas canvas = state.getCanvas();123String type = (r instanceof UIIconRegion ? r.getKey() : "Background");124generate(state, canvas, type);125}126for (UIState state : r.getForegroundStates()) {127Canvas canvas = state.getCanvas();128generate(state, canvas, "Foreground");129}130for (UIState state : r.getBorderStates()) {131Canvas canvas = state.getCanvas();132generate(state, canvas, "Border");133}134//now check for any uiIconRegions, since these are collapsed together.135for (UIRegion sub : r.getSubRegions()) {136if (sub instanceof UIIconRegion) {137generate(sub);138}139}140//generate all the code for component colors141if (!componentColorsMap.isEmpty()) {142getExtendedCacheKeysCode143.append(" protected Object[] getExtendedCacheKeys(JComponent c) {\n")144.append(" Object[] extendedCacheKeys = null;\n")145.append(" switch(state) {\n");146for (Map.Entry<String, List<ComponentColor>> entry : componentColorsMap.entrySet()) {147getExtendedCacheKeysCode148.append(" case ")149.append(entry.getKey()).append(":\n")150.append(" extendedCacheKeys = new Object[] {\n");151for (int i=0; i<entry.getValue().size(); i++) {152ComponentColor cc = entry.getValue().get(i);153cc.write(getExtendedCacheKeysCode);154if (i + 1 < entry.getValue().size()) {155getExtendedCacheKeysCode.append("),\n");156} else {157getExtendedCacheKeysCode.append(")");158}159}160getExtendedCacheKeysCode.append("};\n")161.append(" break;\n");162}163getExtendedCacheKeysCode164.append(" }\n")165.append(" return extendedCacheKeys;\n")166.append(" }");167}168}169170//type is background, foreground, border, upArrowIcon, etc.171private void generate(UIState state, Canvas canvas, String type) {172String states = state.getStateKeys();173String stateType = Utils.statesToConstantName(type + "_" + states);174String paintMethodName = "paint" + type + Utils.statesToClassName(states);175//create new array for component colors for this state176componentColors = new ArrayList<ComponentColor>();177178stateTypeCode.append(" static final int ").append(stateType).append(" = ").append(stateTypeCounter++).append(";\n");179180if (canvas.isBlank()) {181return;182}183184switchCode.append(" case ").append(stateType).append(": ").append(paintMethodName).append("(g); break;\n");185paintingCode.append(" private void ").append(paintMethodName).append("(Graphics2D g) {\n");186187//start by setting up common info needed to encode the control points188Insets in = canvas.getStretchingInsets();189float a = in.left;190float b = canvas.getSize().width - in.right;191float c = in.top;192float d = canvas.getSize().height - in.bottom;193float width = canvas.getSize().width;194float height = canvas.getSize().height;195float cw = b - a;196float ch = d - c;197198Layer[] layers = canvas.getLayers().toArray(new Layer[0]);199for (int index=layers.length-1; index >= 0; index--) {200Layer layer = layers[index];201202//shapes must be painted in reverse order203List<Shape> shapes = layer.getShapes();204for (int i=shapes.size()-1; i>=0; i--) {205Shape shape = shapes.get(i);206Paint paint = shape.getPaint();207208/*209We attempt to write the minimal number of bytecodes as possible when210generating code. Due to the inherit complexities in determining what211is extraneous, we use the following system:212213We first generate the code for the shape. Then, we check to see if214this shape has already been generated. If so, then we defer to an215existing method. If not, then we will create a new methods, stick216the code in it, and refer to that method.217*/218219String shapeMethodName = null; // will contain the name of the method which creates the shape220String shapeVariable = null; // will be one of rect, roundRect, ellipse, or path.221String shapeMethodBody = null;222223if (shape instanceof Rectangle) {224Rectangle rshape = (Rectangle) shape;225float x1 = encode((float)rshape.getX1(), a, b, width);226float y1 = encode((float)rshape.getY1(), c, d, height);227float x2 = encode((float)rshape.getX2(), a, b, width);228float y2 = encode((float)rshape.getY2(), c, d, height);229if (rshape.isRounded()) {230//it is a rounded rectangle231float rounding = (float)rshape.getRounding();232233shapeMethodBody =234" roundRect.setRoundRect(" +235writeDecodeX(x1) + ", //x\n" +236" " + writeDecodeY(y1) + ", //y\n" +237" " + writeDecodeX(x2) + " - " + writeDecodeX(x1) + ", //width\n" +238" " + writeDecodeY(y2) + " - " + writeDecodeY(y1) + ", //height\n" +239" " + rounding + "f, " + rounding + "f); //rounding";240shapeVariable = "roundRect";241} else {242shapeMethodBody =243" rect.setRect(" +244writeDecodeX(x1) + ", //x\n" +245" " + writeDecodeY(y1) + ", //y\n" +246" " + writeDecodeX(x2) + " - " + writeDecodeX(x1) + ", //width\n" +247" " + writeDecodeY(y2) + " - " + writeDecodeY(y1) + "); //height";248shapeVariable = "rect";249}250} else if (shape instanceof Ellipse) {251Ellipse eshape = (Ellipse) shape;252float x1 = encode((float)eshape.getX1(), a, b, width);253float y1 = encode((float)eshape.getY1(), c, d, height);254float x2 = encode((float)eshape.getX2(), a, b, width);255float y2 = encode((float)eshape.getY2(), c, d, height);256shapeMethodBody =257" ellipse.setFrame(" +258writeDecodeX(x1) + ", //x\n" +259" " + writeDecodeY(y1) + ", //y\n" +260" " + writeDecodeX(x2) + " - " + writeDecodeX(x1) + ", //width\n" +261" " + writeDecodeY(y2) + " - " + writeDecodeY(y1) + "); //height";262shapeVariable = "ellipse";263} else if (shape instanceof Path) {264Path pshape = (Path) shape;265List<Point> controlPoints = pshape.getControlPoints();266Point first, last;267first = last = controlPoints.get(0);268StringBuilder buffer = new StringBuilder();269buffer.append(" path.reset();\n");270buffer.append(" path.moveTo(" + writeDecodeX(encode((float)first.getX(), a, b, width)) + ", " + writeDecodeY(encode((float)first.getY(), c, d, height)) + ");\n");271for (int j=1; j<controlPoints.size(); j++) {272Point cp = controlPoints.get(j);273if (last.isP2Sharp() && cp.isP1Sharp()) {274float x = encode((float)cp.getX(), a, b, width);275float y = encode((float)cp.getY(), c, d, height);276buffer.append(" path.lineTo(" + writeDecodeX(x) + ", " + writeDecodeY(y) + ");\n");277} else {278float x1 = encode((float)last.getX(), a, b, width);279float y1 = encode((float)last.getY(), c, d, height);280float x2 = encode((float)cp.getX(), a, b, width);281float y2 = encode((float)cp.getY(), c, d, height);282buffer.append(283" path.curveTo(" + writeDecodeBezierX(x1, last.getX(), last.getCp2X()) + ", "284+ writeDecodeBezierY(y1, last.getY(), last.getCp2Y()) + ", "285+ writeDecodeBezierX(x2, cp.getX(), cp.getCp1X()) + ", "286+ writeDecodeBezierY(y2, cp.getY(), cp.getCp1Y()) + ", "287+ writeDecodeX(x2) + ", " + writeDecodeY(y2) + ");\n");288}289last = cp;290}291if (last.isP2Sharp() && first.isP1Sharp()) {292float x = encode((float)first.getX(), a, b, width);293float y = encode((float)first.getY(), c, d, height);294buffer.append(" path.lineTo(" + writeDecodeX(x) + ", " + writeDecodeY(y) + ");\n");295} else {296float x1 = encode((float)last.getX(), a, b, width);297float y1 = encode((float)last.getY(), c, d, height);298float x2 = encode((float)first.getX(), a, b, width);299float y2 = encode((float)first.getY(), c, d, height);300buffer.append(301" path.curveTo(" + writeDecodeBezierX(x1, last.getX(), last.getCp2X()) + ", "302+ writeDecodeBezierY(y1, last.getY(), last.getCp2Y()) + ", "303+ writeDecodeBezierX(x2, first.getX(), first.getCp1X()) + ", "304+ writeDecodeBezierY(y2, first.getY(), first.getCp1Y()) + ", "305+ writeDecodeX(x2) + ", " + writeDecodeY(y2) + ");\n");306}307buffer.append(" path.closePath();");308shapeMethodBody = buffer.toString();309shapeVariable = "path";310} else {311throw new RuntimeException("Cannot happen unless a new Shape has been defined");312}313314//now that we have the shape defined in shapeMethodBody, and a shapeVariable name,315//look to see if such a body has been previously defined.316shapeMethodName = methods.get(shapeMethodBody);317String returnType = null;318if (shapeMethodName == null) {319if ("rect".equals(shapeVariable)) {320shapeMethodName = "decodeRect" + rectCounter++;321returnType = "Rectangle2D";322} else if ("roundRect".equals(shapeVariable)) {323shapeMethodName = "decodeRoundRect" + roundRectCounter++;324returnType = "RoundRectangle2D";325} else if ("ellipse".equals(shapeVariable)) {326shapeMethodName = "decodeEllipse" + ellipseCounter++;327returnType = "Ellipse2D";328} else {329shapeMethodName = "decodePath" + pathCounter++;330returnType = "Path2D";331}332methods.put(shapeMethodBody, shapeMethodName);333334//since the method wasn't previously defined, time to define it335shapesCode.append(" private ").append(returnType).append(" ").append(shapeMethodName).append("() {\n");336shapesCode.append(shapeMethodBody);337shapesCode.append("\n");338shapesCode.append(" return " + shapeVariable + ";\n");339shapesCode.append(" }\n\n");340}341342//now that the method has been defined, I can go on and decode the343//paint. After the paint is decoded, I can write the g.fill() method call,344//using the result of the shapeMethodName. Yay!345346// if (shapeVariable != null) {347//first, calculate the bounds of the shape being painted and store in variables348paintingCode.append(" ").append(shapeVariable).append(" = ").append(shapeMethodName).append("();\n");349350if (paint instanceof Matte) {351String colorVariable = encodeMatte((Matte)paint);352paintingCode.append(" g.setPaint(").append(colorVariable).append(");\n");353} else if (paint instanceof Gradient) {354String gradientMethodName = encodeGradient(shape, (Gradient)paint);355paintingCode.append(" g.setPaint(").append(gradientMethodName).append("(").append(shapeVariable).append("));\n");356} else if (paint instanceof RadialGradient) {357String radialMethodName = encodeRadial(shape, (RadialGradient)paint);358paintingCode.append(" g.setPaint(").append(radialMethodName).append("(").append(shapeVariable).append("));\n");359}360paintingCode.append(" g.fill(").append(shapeVariable).append(");\n");361}362}363364paintingCode.append("\n }\n\n");365366//collect component colors367if (!componentColors.isEmpty()) {368componentColorsMap.put(stateType, componentColors);369componentColors = null;370}371}372373private float encode(float x, float a, float b, float w) {374float r = 0;375if (x < a) {376r = (x / a);377} else if (x > b) {378r = 2 + ((x - b) / (w - b));379} else if (x == a && x == b) {380return 1.5f;381} else {382r = 1 + ((x - a) / (b - a));383}384385if (Float.isNaN(r)) {386System.err.println("[Error] Encountered NaN: encode(" + x + ", " + a + ", " + b + ", " + w + ")");387return 0;388} else if (Float.isInfinite(r)) {389System.err.println("[Error] Encountered Infinity: encode(" + x + ", " + a + ", " + b + ", " + w + ")");390return 0;391} else if (r < 0) {392System.err.println("[Error] encoded value was less than 0: encode(" + x + ", " + a + ", " + b + ", " + w + ")");393return 0;394} else if (r > 3) {395System.err.println("[Error] encoded value was greater than 3: encode(" + x + ", " + a + ", " + b + ", " + w + ")");396return 3;397} else {398return r;399}400}401402private String writeDecodeX(float encodedX) {403return "decodeX(" + encodedX + "f)";404}405406private String writeDecodeY(float encodedY) {407return "decodeY(" + encodedY + "f)";408}409410/**411*412* @param ex encoded x value413* @param x unencoded x value414* @param cpx unencoded cpx value415* @return416*/417private static String writeDecodeBezierX(double ex, double x, double cpx) {418return "decodeAnchorX(" + ex + "f, " + (cpx - x) + "f)";419}420421/**422*423* @param ey encoded y value424* @param y unencoded y value425* @param cpy unencoded cpy value426* @return427*/428private static String writeDecodeBezierY(double ey, double y, double cpy) {429return "decodeAnchorY(" + ey + "f, " + (cpy - y) + "f)";430}431432private String encodeMatte(Matte m) {433String declaration = m.getDeclaration();434String variableName = colors.get(declaration);435if (variableName == null) {436variableName = "color" + colorCounter++;437colors.put(declaration, variableName);438colorCode.append(String.format(" private Color %s = %s;\n",439variableName, declaration));440}441// handle component colors442if (m.getComponentPropertyName() != null) {443ComponentColor cc = m.createComponentColor(variableName);444int index = componentColors.indexOf(cc);445if (index == -1) {446index = componentColors.size();447componentColors.add(cc);448}449return "(Color)componentColors[" + index + "]";450} else {451return variableName;452}453}454455private String encodeGradient(Shape ps, Gradient g) {456StringBuilder b = new StringBuilder();457float x1 = (float)ps.getPaintX1();458float y1 = (float)ps.getPaintY1();459float x2 = (float)ps.getPaintX2();460float y2 = (float)ps.getPaintY2();461b.append(" return decodeGradient((");462b.append(x1);463b.append("f * w) + x, (");464b.append(y1);465b.append("f * h) + y, (");466b.append(x2);467b.append("f * w) + x, (");468b.append(y2);469b.append("f * h) + y,\n");470encodeGradientColorsAndFractions(g,b);471b.append(");");472473String methodBody = b.toString();474String methodName = methods.get(methodBody);475if (methodName == null) {476methodName = "decodeGradient" + gradientCounter++;477gradientsCode.append(" private Paint ").append(methodName).append("(Shape s) {\n");478gradientsCode.append(" Rectangle2D bounds = s.getBounds2D();\n");479gradientsCode.append(" float x = (float)bounds.getX();\n");480gradientsCode.append(" float y = (float)bounds.getY();\n");481gradientsCode.append(" float w = (float)bounds.getWidth();\n");482gradientsCode.append(" float h = (float)bounds.getHeight();\n");483gradientsCode.append(methodBody);484gradientsCode.append("\n }\n\n");485methods.put(methodBody, methodName);486}487return methodName;488}489490/**491* Takes a abstract gradient and creates the code for the fractions float492* array and the colors array that can be used in the constructors of linear493* and radial gradients.494*495* @param g The abstract gradient to get stops from496* @param b Append code string of the form "new float[]{...},497* new Color[]{...}" to this StringBuilder498*/499private void encodeGradientColorsAndFractions(AbstractGradient g,500StringBuilder b) {501List<GradientStop> stops = g.getStops();502// there are stops.size() number of main stops. Between each is a503// fractional stop. Thus, there are: stops.size() + stops.size() - 1504// number of fractions and colors.505float[] fractions = new float[stops.size() + stops.size() - 1];506String[] colors = new String[fractions.length];507//for each stop, create the stop and it's associated fraction508int index = 0; // the index into fractions and colors509for (int i = 0; i < stops.size(); i++) {510GradientStop s = stops.get(i);511//copy over the stop's data512colors[index] = encodeMatte(s.getColor());513fractions[index] = s.getPosition();514515//If this isn't the last stop, then add in the fraction516if (index < fractions.length - 1) {517float f1 = s.getPosition();518float f2 = stops.get(i + 1).getPosition();519index++;520fractions[index] = f1 + (f2 - f1) * s.getMidpoint();521colors[index] = "decodeColor("+522colors[index - 1]+","+523encodeMatte(stops.get(i + 1).getColor())+",0.5f)";524}525index++;526}527// Check boundry conditions528for (int i = 1; i < fractions.length; i++) {529//to avoid an error with LinearGradientPaint where two fractions530//are identical, bump up the fraction value by a miniscule amount531//if it is identical to the previous one532//NOTE: The <= is critical because the previous value may already533//have been bumped up534if (fractions[i] <= fractions[i - 1]) {535fractions[i] = fractions[i - 1] + .000001f;536}537}538//another boundary condition where multiple stops are all at the end. The539//previous loop bumped all but one of these past 1.0, which is bad.540//so remove any fractions (and their colors!) that are beyond 1.0541int outOfBoundsIndex = -1;542for (int i = 0; i < fractions.length; i++) {543if (fractions[i] > 1) {544outOfBoundsIndex = i;545break;546}547}548if (outOfBoundsIndex >= 0) {549float[] f = fractions;550String[] c = colors;551fractions = new float[outOfBoundsIndex];552colors = new String[outOfBoundsIndex];553System.arraycopy(f, 0, fractions, 0, outOfBoundsIndex);554System.arraycopy(c, 0, colors, 0, outOfBoundsIndex);555}556// build string557b.append(" new float[] { ");558for (int i = 0; i < fractions.length; i++) {559if (i>0)b.append(',');560b.append(fractions[i]);561b.append('f');562}563b.append(" },\n new Color[] { ");564for (int i = 0; i < colors.length; i++) {565if (i>0) b.append(",\n ");566b.append(colors[i]);567}568b.append("}");569}570571private String encodeRadial(Shape ps, RadialGradient g) {572float centerX1 = (float)ps.getPaintX1();573float centerY1 = (float)ps.getPaintY1();574float x2 = (float)ps.getPaintX2();575float y2 = (float)ps.getPaintY2();576float radius = (float)Point2D.distance(centerX1, centerY1, x2, y2);577StringBuilder b = new StringBuilder();578579b.append(" return decodeRadialGradient((");580b.append(centerX1);581b.append("f * w) + x, (");582b.append(centerY1);583b.append("f * h) + y, ");584b.append(radius);585b.append("f,\n");586encodeGradientColorsAndFractions(g,b);587b.append(");");588589String methodBody = b.toString();590String methodName = methods.get(methodBody);591if (methodName == null) {592methodName = "decodeRadial" + radialCounter++;593gradientsCode.append(" private Paint ").append(methodName).append("(Shape s) {\n");594gradientsCode.append(" Rectangle2D bounds = s.getBounds2D();\n");595gradientsCode.append(" float x = (float)bounds.getX();\n");596gradientsCode.append(" float y = (float)bounds.getY();\n");597gradientsCode.append(" float w = (float)bounds.getWidth();\n");598gradientsCode.append(" float h = (float)bounds.getHeight();\n");599gradientsCode.append(methodBody);600gradientsCode.append("\n }\n\n");601methods.put(methodBody, methodName);602}603return methodName;604}605606//note that this method is not thread-safe. In fact, none of this class is.607public static void writePainter(UIRegion r, String painterName) {608//Need only write out the stuff for this region, don't need to worry about subregions609//since this method will be called for each of those (and they go in their own file, anyway).610//The only subregion that we compound into this is the one for icons.611PainterGenerator gen = new PainterGenerator(r);612System.out.println("Generating source file: " + painterName + ".java");613614Map<String, String> variables = Generator.getVariables();615variables.put("PAINTER_NAME", painterName);616variables.put("STATIC_DECL", gen.stateTypeCode.toString());617variables.put("COLORS_DECL", gen.colorCode.toString());618variables.put("DO_PAINT_SWITCH_BODY", gen.switchCode.toString());619variables.put("PAINTING_DECL", gen.paintingCode.toString());620variables.put("GET_EXTENDED_CACHE_KEYS", gen.getExtendedCacheKeysCode.toString());621variables.put("SHAPES_DECL", gen.shapesCode.toString());622variables.put("GRADIENTS_DECL", gen.gradientsCode.toString());623624Generator.writeSrcFile("PainterImpl", variables, painterName);625}626}627628629