/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: TechExplorer.java
 * Written by: Dmitry Nadezhin, Sun Microsystems.
 *
 * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.sandbox;

import com.sun.electric.database.geometry.EGraphics;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ERectangle;
import com.sun.electric.technology.DRCTemplate;
import com.sun.electric.technology.EdgeH;
import com.sun.electric.technology.EdgeV;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.Xml;
import com.sun.electric.util.math.DBMath;

import java.awt.Color;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.prefs.Preferences;

/**
 * Main class of stand-alone process which loads "electric.jar (possibly with old Electric version),
 * initializes technologies and it and executes different test commands.
 */
public class TechExplorer extends ESandBox {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        try {
            String fileName = args[0];
            File electricJar = new File(fileName);
            if (!electricJar.exists())
                throw new FileNotFoundException(fileName);
            TechExplorer m = new TechExplorer(electricJar);

            InputStream commandStream = System.in;
            if (args.length >= 2) {
                commandStream = new BufferedInputStream(new FileInputStream(args[1]));
            }
            m.loop(commandStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private TechExplorer(File electricJar) throws IOException, ClassNotFoundException, MalformedURLException, IllegalAccessException {
        super(electricJar.toURI().toURL());
    }

    public String[] initTechnologies(String args) throws IllegalAccessException, InvocationTargetException {
        if (Undo_changesQuiet != null)
            Undo_changesQuiet.invoke(null, Boolean.TRUE);
        if (Tool_initProjectSettings != null)
            Tool_initProjectSettings.invoke(User_getUserTool.invoke(null));
//            Tool_initAllTools.invoke(null);
        Technology_initAllTechnologies.invoke(null);
        List<String> technologies = new ArrayList<String>();
        for (Iterator<?> tit = (Iterator)Technology_getTechnologies.invoke(null); tit.hasNext(); ) {
            Object tech = tit.next();
            String techName = (String)Technology_getTechName.invoke(tech);
            technologies.add(techName);
        }
        return technologies.toArray(new String[technologies.size()]);
    }

    public void dumpAll(String fileName) throws IllegalAccessException, InvocationTargetException {
        for (Iterator<?> tit = (Iterator)Technology_getTechnologies.invoke(null); tit.hasNext(); ) {
            Object tech = tit.next();
            String techName = (String)Technology_getTechName.invoke(tech);
            System.out.println("Technology " + techName);
            Xml.Technology t = makeXml(techName);
            t.writeXml(fileName.replaceAll("lst", techName + "\\.xml"));
        }
//        if (Setting_getSettings != null) {
//            Collection<?> allSettings = (Collection)Setting_getSettings.invoke(null);
//            for (Object setting: allSettings) {
//                String xmlPath = (String)Setting_getXmlPath.invoke(setting);
//                Preferences prefs = (Preferences)Setting_prefs.get(setting);
//                String prefName = (String)Setting_getPrefName.invoke(setting);
//                String prefPath = prefs.absolutePath() + "/" + prefName;
//                Object factoryValue = Setting_getFactoryValue.invoke(setting);
//                assert xmlPath.length() > 0;
//            }
//        }
//        List<?> allPrefs = (List)Pref_allPrefs.get(null);
//        for (Object pref: allPrefs) {
//            Preferences prefs = (Preferences)Pref_prefs.get(pref);
//            String prefName = (String)Pref_getPrefName.invoke(pref);
//            String prefPath = prefs.absolutePath() + "/" + prefName;
//            boolean isMeaning = Pref_getMeaning != null && Pref_getMeaning.invoke(pref) != null;
//            Object factoryValue = Pref_getFactoryValue.invoke(pref);
//        }
    }

    public void dumpPrefs(String fileName) throws IOException {
        PrintWriter out = new PrintWriter(fileName);
        DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(fileName.replaceAll("\\.lst", "\\.bin"))));
        dumpPrefs(out, dout);
        out.close();
        dout.close();
    }

    private void dumpPrefs(PrintWriter out, DataOutputStream dout) throws IOException {
        try {
            String version = (String)Version_getVersion.invoke(null).toString();
            out.println("Version " + version); dout.writeUTF(version);
            for (Iterator<?> tit = (Iterator)Technology_getTechnologies.invoke(null); tit.hasNext(); ) {
                Object tech = tit.next();
                String techName = (String)Technology_getTechName.invoke(tech);
                assert techName.length() > 0;
                out.println("Technology " + techName); dout.writeUTF(techName);

                for (Iterator<?> it = (Iterator)Technology_getLayers.invoke(tech); it.hasNext(); ) {
                    Object layer = it.next();
                    String layerName = (String)Layer_getName.invoke(layer);
                    assert layerName.length() > 0;
                    Object pseudoLayer = null;
                    if (Layer_getPseudoLayer != null)
                        pseudoLayer = Layer_getPseudoLayer.invoke(layer);
                    out.print("Layer " + layerName); dout.writeUTF(layerName);
                    String pseudoLayerName = "";
                    if (pseudoLayer != null) {
                        pseudoLayerName = (String)Layer_getName.invoke(pseudoLayer);
                        assert pseudoLayerName.length() > 0;
                        out.print(" " + pseudoLayerName);
                    }
                    dout.writeUTF(pseudoLayerName);
                    out.println();
                }
                dout.writeUTF("");
                for (Iterator<?> it = (Iterator)Technology_getArcs.invoke(tech); it.hasNext(); ) {
                    Object ap = it.next();
                    String arcName = (String)ArcProto_getName.invoke(ap);
                    out.println("Arc " + arcName); dout.writeUTF(arcName);
                }
                dout.writeUTF("");
                for (Iterator<?> it = (Iterator)Technology_getNodes.invoke(tech); it.hasNext(); ) {
                    Object pn = it.next();
                    String nodeName = (String)PrimitiveNode_getName.invoke(pn);
                    out.println("Node " + nodeName); dout.writeUTF(nodeName);
                }
                dout.writeUTF("");
            }
            dout.writeUTF("");
            if (Setting_getSettings != null) {
                Collection<?> allSettings = (Collection)Setting_getSettings.invoke(null);
                for (Object setting: allSettings) {
                    String xmlPath = (String)Setting_getXmlPath.invoke(setting);
                    Preferences prefs = (Preferences)Setting_prefs.get(setting);
                    String prefName = (String)Setting_getPrefName.invoke(setting);
                    String prefPath = prefs.absolutePath() + "/" + prefName;
                    Object factoryValue = Setting_getFactoryValue.invoke(setting);
                    out.println("Setting " + xmlPath + " " + prefPath + " <" + factoryValue + ">");
                    assert xmlPath.length() > 0;
                    dout.writeUTF(xmlPath);
                    dout.writeUTF(prefPath);
                }
            }
            dout.writeUTF("");
            List<?> allPrefs = (List)Pref_allPrefs.get(null);
            for (Object pref: allPrefs) {
                Preferences prefs = (Preferences)Pref_prefs.get(pref);
                String prefName = (String)Pref_getPrefName.invoke(pref);
                String prefPath = prefs.absolutePath() + "/" + prefName;
                boolean isMeaning = Pref_getMeaning != null && Pref_getMeaning.invoke(pref) != null;
                Object factoryValue = Pref_getFactoryValue.invoke(pref);
                out.println((isMeaning ? "Mean " : "Pref ") +  prefPath + " <" + factoryValue + ">");
                dout.writeUTF(prefPath); dout.writeBoolean(isMeaning);
            }
            dout.writeUTF("");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

 //    private void dumpShape(Object tech, Object pn) throws IllegalAccessException, InvocationTargetException, InstantiationException {
//        Object lib = Library_newInstance.invoke(null, "l", null);
//        String cellName = "a;1{lay}";
//        Object cell = null;
//        if (classCellVersionGroup != null) {
//            cell = Cell_lowLevelAllocate.invoke(null, lib);
//            Object versionGroup = CellVersionGroup_constructor.newInstance();
//            CellVersionGroup_add.invoke(versionGroup, cell);
//            Cell_lowLevelPopulate.invoke(cell, cellName);
//            Cell_lowLevelLink.invoke(cell);
//        } else {
//            cell = Cell_newInstance.invoke(null, lib, cellName);
//        }
//        Object ni = null;
//        if (NodeInst_newInstance1 != null)
//            ni = NodeInst_newInstance1.invoke(null, pn, new Point2D.Double(), Double.valueOf(10), Double.valueOf(10), cell);
//        else if (NodeInst_newInstance2 != null)
//            ni = NodeInst_newInstance2.invoke(null, pn, new Point2D.Double(), Double.valueOf(10), Double.valueOf(10), Integer.valueOf(0), cell, null);
//        Object[] polys = null;
//        if (Technology_getShapeOfNode1 != null)
//            polys = (Object[])Technology_getShapeOfNode1.invoke(tech, ni, Boolean.FALSE, Boolean.FALSE, null);
//        else if (Technology_getShapeOfNode2 != null)
//            polys = (Object[])Technology_getShapeOfNode2.invoke(tech, ni, null, null, Boolean.FALSE, Boolean.FALSE, null);
//        else if (Technology_getShapeOfNode3 != null)
//            polys = (Object[])Technology_getShapeOfNode3.invoke(tech, ni, null, null, Boolean.FALSE, Boolean.FALSE, null);
//        else if (Technology_getShapeOfNode4 != null)
//            polys = (Object[])Technology_getShapeOfNode4.invoke(tech, ni, null, null, Boolean.FALSE, Boolean.FALSE, null);
//        else if (Technology_getShapeOfNode5 != null)
//            polys = (Object[])Technology_getShapeOfNode5.invoke(tech, ni, null, Boolean.FALSE, Boolean.FALSE, null);
//        else if (Technology_getShapeOfNode6 != null)
//            polys = (Object[])Technology_getShapeOfNode6.invoke(tech, ni, null, Boolean.FALSE, Boolean.FALSE);
//        for (Object poly: polys) {
//            System.out.print("Poly " + Poly_getStyle.invoke(poly).toString());
//            Point2D[] points = (Point2D[])Poly_getPoints.invoke(poly);
//            for (Point2D p: points)
//                System.out.print(" " + p.getX() + "," + p.getY());
//            System.out.println();
//        }
//
//    }

    public Xml.Technology makeXml(String techName) throws IllegalAccessException, InvocationTargetException {
        Object tech = Technology_findTechnology.invoke(null, techName);

        Xml.Technology t = new Xml.Technology();
        t.techName = techName;
        t.className = tech.getClass().getName();
        if (t.className.equals("com.sun.electric.technology.Technology"))
            t.className = null;

        Xml.Version version;
        version = new Xml.Version();
        version.techVersion = 1;
        version.electricVersion = Technology.DISK_VERSION_1;
        t.versions.add(version);
        version = new Xml.Version();
        version.techVersion = 2;
        version.electricVersion = Technology.DISK_VERSION_2;
        t.versions.add(version);

        t.shortTechName = (String)Technology_getTechShortName.invoke(tech);
        t.description = (String)Technology_getTechDesc.invoke(tech);
        t.scaleValue = (Double)Technology_getScale.invoke(tech);
        t.scaleRelevant = (Boolean)Technology_isScaleRelevant.invoke(tech);
        t.resolutionValue = (Double)Technology_getResolution.invoke(tech);
        t.defaultFoundry = "NONE";
        if (Technology_getPrefFoundry != null)
            t.defaultFoundry = Technology_getPrefFoundry.invoke(tech).toString();
        t.minResistance = (Double)Technology_getMinResistance.invoke(tech);
        t.minCapacitance = (Double)Technology_getMinCapacitance.invoke(tech);
        int numTransparentLayers = (Integer)Technology_getNumTransparentLayers.invoke(tech);
        if (numTransparentLayers > 0) {
            Color[] colorMap = (Color[])Technology_getColorMap.invoke(tech);
            for (int i = 0; i < numTransparentLayers; i++) {
                Color transparentColor = colorMap[1 << i];
                t.transparentLayers.add(transparentColor);
            }
        }
//        makeFoundries(t, tech);

        int maxMetal = 0;
        for (Iterator<?> it = (Iterator)Technology_getLayers.invoke(tech); it.hasNext(); ) {
            Object layer = it.next();
            if (isPseudoLayer(layer)) continue;
            String layerName = (String)Layer_getName.invoke(layer);

            Xml.Layer l = new Xml.Layer();
            l.name = layerName;
            Object fun = Layer_getFunction.invoke(layer);
            l.function = fun != null ? LayerFunctions.get(fun) : Layer.Function.UNKNOWN;
            if (l.function.isMetal())
                maxMetal = Math.max(maxMetal, l.function.getLevel());
            l.extraFunction = (Integer)Layer_getFunctionExtras.invoke(layer);
            Object desc = Layer_getGraphics.invoke(layer);
            boolean displayPatterned = (Boolean)EGraphics_isPatternedOnDisplay.invoke(desc);
            boolean printPatterned = (Boolean)EGraphics_isPatternedOnPrinter.invoke(desc);
            EGraphics.Outline outlineWhenPatterned = EGraphics.Outline.NOPAT;
            if (EGraphics_getOutlined != null) {
                Object outline = EGraphics_getOutlined.invoke(desc);
                if (outline != null)
                    outlineWhenPatterned = EGraphicsOutlines.get(outline);
            } else {
                if (EGraphics_isOutlinedOnDisplay != null && (Boolean)EGraphics_isOutlinedOnDisplay.invoke(desc))
                    outlineWhenPatterned = EGraphics.Outline.PAT_S;
                if (EGraphics_isOutlinedOnPrinter != null && (Boolean)EGraphics_isOutlinedOnPrinter.invoke(desc))
                    outlineWhenPatterned = EGraphics.Outline.PAT_S;
            }
            int transparentLayer = (Integer)EGraphics_getTransparentLayer.invoke(desc);
            Color color = (Color)EGraphics_getColor.invoke(desc);
            double opacity = (Double)EGraphics_getOpacity.invoke(desc);
            boolean foreground = (Boolean)EGraphics_getForeground.invoke(desc);
            int[] pattern = (int[])EGraphics_getPattern.invoke(desc);
            EGraphics.J3DTransparencyOption mode = EGraphics.DEFAULT_MODE;
            if (Layer_getTransparencyMode != null)
                mode = EGraphics.J3DTransparencyOption.valueOf((String)Layer_getTransparencyMode.invoke(layer));
            double factor = EGraphics.DEFAULT_FACTOR;
            if (Layer_getTransparencyFactor != null)
                factor = (Double)Layer_getTransparencyFactor.invoke(layer);
            l.desc = new EGraphics(displayPatterned, printPatterned, outlineWhenPatterned,
                    transparentLayer, color.getRed(), color.getGreen(), color.getBlue(), opacity, foreground, pattern, mode, factor);
            l.thick3D = (Double)Layer_getThickness.invoke(layer);
            if (Layer_getDistance != null)
                l.height3D = (Double)Layer_getDistance.invoke(layer);
            else if (Layer_getHeight != null)
                l.height3D = (Double)Layer_getHeight.invoke(layer);
            l.cif = (String)Layer_getCIFLayer.invoke(layer);
            l.skill = (String)Layer_getSkillLayer.invoke(layer);
            l.resistance = (Double)Layer_getResistance.invoke(layer);
            l.capacitance = (Double)Layer_getCapacitance.invoke(layer);
            l.edgeCapacitance = (Double)Layer_getEdgeCapacitance.invoke(layer);
            t.layers.add(l);
        }
        if (Technology_getNumMetals != null)
            maxMetal = (Integer)Technology_getNumMetals.invoke(tech);
        t.minNumMetals = t.maxNumMetals = t.defaultNumMetals = maxMetal;

        Map<String,?> oldArcNames = Technology_getOldArcNames != null ? (Map)Technology_getOldArcNames.invoke(tech) : Collections.emptyMap();
        for (Iterator<?> it = (Iterator)Technology_getArcs.invoke(tech); it.hasNext(); ) {
            Object ap = it.next();
            String arcName = (String)ArcProto_getName.invoke(ap);

            Xml.ArcProto a = new Xml.ArcProto();
            a.name = arcName;
            for (Map.Entry<String,?> e: oldArcNames.entrySet()) {
                if (e.getValue() == ap)
                    a.oldName = e.getKey();
            }
            a.function = ArcProtoFunctions.get(ArcProto_getFunction.invoke(ap));
            a.wipable = (Boolean)ArcProto_isWipable.invoke(ap);
            a.curvable = (Boolean)ArcProto_isCurvable.invoke(ap);
            a.special = ArcProto_isSpecialArc != null && (Boolean)ArcProto_isSpecialArc.invoke(ap);
            a.skipSizeInPalette = ArcProto_isSkipSizeInPalette != null && (Boolean)ArcProto_isSkipSizeInPalette.invoke(ap);
            a.notUsed = (Boolean)ArcProto_isNotUsed.invoke(ap);
            a.extended = (Boolean)ArcProto_isExtended.invoke(ap);
            a.fixedAngle = (Boolean)ArcProto_isFixedAngle.invoke(ap);
            a.angleIncrement = (Integer)ArcProto_getAngleIncrement.invoke(ap);
            if (ERC_getAntennaRatio != null)
                a.antennaRatio = (Double)ERC_getAntennaRatio.invoke(ERC_tool.get(null), ap);
            else if (ArcProto_getAntennaRatio != null)
                a.antennaRatio = (Double)ArcProto_getAntennaRatio.invoke(ap);

            double defaultFullWidth = 0;
            if (ArcProto_getLambdaElibWidthOffset != null && ArcProto_getDefaultLambdaBaseWidth != null)
                defaultFullWidth = (Double)ArcProto_getLambdaElibWidthOffset.invoke(ap) + (Double)ArcProto_getDefaultLambdaBaseWidth.invoke(ap);
            else if (ArcProto_getDefaultLambdaFullWidth != null)
                defaultFullWidth = (Double)ArcProto_getDefaultLambdaFullWidth.invoke(ap);
            else if (ArcProto_getDefaultWidth != null)
                defaultFullWidth = (Double)ArcProto_getDefaultWidth.invoke(ap);
            double widthOffset = 0;
            if (ArcProto_getLambdaElibWidthOffset != null)
                widthOffset = (Double)ArcProto_getLambdaElibWidthOffset.invoke(ap);
            else if (ArcProto_getLambdaWidthOffset != null)
                widthOffset = (Double)ArcProto_getLambdaWidthOffset.invoke(ap);
            else if (ArcProto_getWidthOffset != null)
                widthOffset = (Double)ArcProto_getWidthOffset.invoke(ap);
            if (widthOffset != 0) {
                a.diskOffset.put(Integer.valueOf(1), round(0.5*defaultFullWidth));
                a.diskOffset.put(Integer.valueOf(2), round(0.5*(defaultFullWidth - widthOffset)));
            } else {
                a.diskOffset.put(Integer.valueOf(2), round(0.5*defaultFullWidth));
            }
            Object[] arcLayers = (Object[])ArcProto_layers.get(ap);
            for (Object arcLayer: arcLayers) {
                Xml.ArcLayer al = new Xml.ArcLayer();
                al.layer = (String)Layer_getName.invoke(TechnologyArcLayer_getLayer.invoke(arcLayer));
                al.style = PolyTypes.get(TechnologyArcLayer_getStyle.invoke(arcLayer));
                double extend = 0;
                if (TechnologyArcLayer_getGridExtend != null) {
                    extend = DBMath.gridToLambda((Integer)TechnologyArcLayer_getGridExtend.invoke(arcLayer));
                } else {
                    double offset = 0;
                    if (TechnologyArcLayer_getLambdaOffset != null)
                        offset = (Double)TechnologyArcLayer_getLambdaOffset.invoke(arcLayer);
                    else if (TechnologyArcLayer_getOffset != null)
                        offset = (Double)TechnologyArcLayer_getOffset.invoke(arcLayer);
                    extend = 0.5*(defaultFullWidth - offset);
                }
                al.extend.addLambda(round(extend));
                a.arcLayers.add(al);
            }
            t.arcs.add(a);
        }

        Map<String,?> oldNodeNames = Technology_getOldNodeNames != null ? (Map)Technology_getOldNodeNames.invoke(tech) : Collections.emptyMap();
        for (Iterator<?> it = (Iterator)Technology_getNodes.invoke(tech); it.hasNext(); ) {
            Object pn = it.next();
            String nodeName = (String)PrimitiveNode_getName.invoke(pn);
            PrimitiveNode.Function fun = PrimitiveNodeFunctions.get(PrimitiveNode_getFunction.invoke(pn));
            Object[] nodeLayersArray = (Object[])PrimitiveNode_getLayers.invoke(pn);
            double defWidth = (Double)PrimitiveNode_getDefWidth.invoke(pn);
            double defHeight = (Double)PrimitiveNode_getDefHeight.invoke(pn);
            Iterator<?> ports = (Iterator)PrimitiveNode_getPorts.invoke(pn);
            if (fun == PrimitiveNode.Function.NODE && nodeLayersArray.length == 1) {
                Xml.PureLayerNode pln = new Xml.PureLayerNode();
                pln.name = nodeName;
                for (Map.Entry<String,?> e: oldNodeNames.entrySet()) {
                    if (e.getValue() == pn)
                        pln.oldName = e.getKey();
                }
                Object port = ports.next();
                pln.port = (String)PrimitivePort_getName.invoke(port);
                pln.style = PolyTypes.get(TechnologyNodeLayer_getStyle.invoke(nodeLayersArray[0]));
                pln.size.addLambda(round(defWidth));
                makePortArcs(pln.portArcs, tech, port, null);
                Xml.Layer layer = t.findLayer((String)Layer_getName.invoke(TechnologyNodeLayer_getLayer.invoke(nodeLayersArray[0])));
                layer.pureLayerNode = pln;
                continue;
            }

            Xml.PrimitiveNodeGroup ng = new Xml.PrimitiveNodeGroup();
            ng.isSingleton = true;
            Xml.PrimitiveNode n = new Xml.PrimitiveNode();
            ng.nodes.add(n);
            n.name = nodeName;
            for (Map.Entry<String,?> e: oldNodeNames.entrySet()) {
                if (e.getValue() == pn)
                    n.oldName = e.getKey();
            }
            n.function = fun;
            ng.shrinkArcs = (Boolean)PrimitiveNode_isArcsShrink.invoke(pn);
            ng.partialCircle = (Boolean)PrimitiveNode_isPartialCircle.invoke(pn);
            ng.square = (Boolean)PrimitiveNode_isSquare.invoke(pn);
            ng.canBeZeroSize = (Boolean)PrimitiveNode_isCanBeZeroSize.invoke(pn);
            ng.wipes = (Boolean)PrimitiveNode_isWipeOn1or2.invoke(pn);
            ng.lockable = (Boolean)PrimitiveNode_isLockedPrim.invoke(pn);
            ng.edgeSelect = (Boolean)PrimitiveNode_isEdgeSelect.invoke(pn);
            if (PrimitiveNode_isSkipSizeInPalette != null)
                ng.skipSizeInPalette = (Boolean)PrimitiveNode_isSkipSizeInPalette.invoke(pn);
            ng.notUsed = (Boolean)PrimitiveNode_isNotUsed.invoke(pn);
            if (PrimitiveNode_LOWVTBIT != null)
                n.lowVt = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_LOWVTBIT.get(null));
            if (PrimitiveNode_HIGHVTBIT != null)
                n.highVt = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_HIGHVTBIT.get(null));
            if (PrimitiveNode_NATIVEBIT != null)
                n.nativeBit = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_NATIVEBIT.get(null));
            if (PrimitiveNode_OD18BIT != null)
                n.od18 = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_OD18BIT.get(null));
            if (PrimitiveNode_OD25BIT != null)
                n.od25 = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_OD25BIT.get(null));
            if (PrimitiveNode_OD33BIT != null)
                n.od33 = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_OD33BIT.get(null));
            if (PrimitiveNode_CURVEPIN != null)
                n.curvePin = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_CURVEPIN.get(null));

            EPoint sizeCorrector1 = null;
            EPoint sizeCorrector2 = null;
            if (PrimitiveNode_getSizeCorrector != null) {
                sizeCorrector1 = EPoint.snap((Point2D)PrimitiveNode_getSizeCorrector.invoke(pn, 0));
                sizeCorrector2 = EPoint.snap((Point2D)PrimitiveNode_getSizeCorrector.invoke(pn, 1));
            }
            double minWidth = 0, minHeight = 0;
            String minSizeRule = null;
            if (classPrimitiveNodeNodeSizeRule != null) {
                Object rule = PrimitiveNode_getMinSizeRule.invoke(pn);
                if (rule != null) {
                    minWidth = (Double)PrimitiveNodeNodeSizeRule_getWidth.invoke(rule);
                    minHeight = (Double)PrimitiveNodeNodeSizeRule_getHeight.invoke(rule);
                    minSizeRule = (String)PrimitiveNodeNodeSizeRule_getRuleName.invoke(rule);
                }
            } else {
                minWidth = (Double)PrimitiveNode_getMinWidth.invoke(pn);
                minHeight = (Double)PrimitiveNode_getMinHeight.invoke(pn);
                minSizeRule = (String)PrimitiveNode_getMinSizeRule.invoke(pn);
                if (minWidth == -1 && minHeight == -1 && minSizeRule.equals(""))
                    minSizeRule = null;
            }
            EPoint fullSize = null;
            if (minSizeRule != null) {
                ng.nodeSizeRule = new Xml.NodeSizeRule();
                ng.nodeSizeRule.width = minWidth;
                ng.nodeSizeRule.height = minHeight;
                ng.nodeSizeRule.rule = minSizeRule;
                fullSize = EPoint.fromLambda(0.5*minWidth, 0.5*minHeight);
            } else if (PrimitiveNode_getFullRectangle != null) {
                Rectangle2D r = (Rectangle2D)PrimitiveNode_getFullRectangle.invoke(pn);
                fullSize = EPoint.fromLambda(0.5*r.getWidth(), 0.5*r.getHeight());
            } else {
                fullSize = sizeCorrector1;
            }
            if (fullSize == null)
                fullSize = EPoint.fromLambda(0.5*defWidth, 0.5*defHeight);
            ERectangle fullRectangle = ERectangle.fromLambda(-fullSize.getX(), -fullSize.getY(),
                    2*fullSize.getX(), 2*fullSize.getY());
            ERectangle baseRectangle;
            if (PrimitiveNode_getBaseRectangle != null) {
                baseRectangle = ERectangle.fromLambda((Rectangle2D)PrimitiveNode_getBaseRectangle.invoke(pn));
            } else {
                double lx = fullRectangle.getLambdaMinX();
                double hx = fullRectangle.getLambdaMaxX();
                double ly = fullRectangle.getLambdaMinY();
                double hy = fullRectangle.getLambdaMaxY();
                Object sizeOffset = PrimitiveNode_getProtoSizeOffset.invoke(pn);
                if (sizeOffset != null) {
                    lx += (Double)SizeOffset_getLowXOffset.invoke(sizeOffset);
                    hx -= (Double)SizeOffset_getHighXOffset.invoke(sizeOffset);
                    ly += (Double)SizeOffset_getLowYOffset.invoke(sizeOffset);
                    hy -= (Double)SizeOffset_getHighYOffset.invoke(sizeOffset);
                }
                baseRectangle = ERectangle.fromLambda(lx, ly, hx - lx, hy - ly);
            }
            ng.baseLX.value = baseRectangle.getLambdaMinX();
            ng.baseHX.value = baseRectangle.getLambdaMaxX();
            ng.baseLY.value = baseRectangle.getLambdaMinY();
            ng.baseHY.value = baseRectangle.getLambdaMaxY();
//            if (!baseRectangle.equals(fullRectangle)) {
//                n.sizeOffset = new SizeOffset(
//                    baseRectangle.getLambdaMinX() - fullRectangle.getLambdaMinX(),
//                    fullRectangle.getLambdaMaxX() - baseRectangle.getLambdaMaxX(),
//                    baseRectangle.getLambdaMinY() - fullRectangle.getLambdaMinY(),
//                    fullRectangle.getLambdaMaxY() - baseRectangle.getLambdaMaxY());
//            }
            if (sizeCorrector1 == null)
                sizeCorrector1 = fullSize;
            if (sizeCorrector2 == null)
                sizeCorrector2 = EPoint.fromGrid(baseRectangle.getGridWidth() >> 1, baseRectangle.getGridHeight() >> 1);
            if (!sizeCorrector2.equals(sizeCorrector1))
                ng.diskOffset.put(Integer.valueOf(1), sizeCorrector1);
            if (!sizeCorrector2.equals(EPoint.ORIGIN))
                ng.diskOffset.put(Integer.valueOf(2), sizeCorrector2);
            ng.defaultWidth.addLambda(round(defWidth - fullRectangle.getLambdaWidth()));
            ng.defaultHeight.addLambda(round(defHeight - fullRectangle.getLambdaHeight()));

            List<?> nodeLayers = Arrays.asList(nodeLayersArray);
            Object[] electricalNodeLayersArray = (Object[])PrimitiveNode_getElectricalLayers.invoke(pn);
            List<?> electricalNodeLayers = nodeLayers;
            if (electricalNodeLayersArray != null)
                electricalNodeLayers = Arrays.asList(electricalNodeLayersArray);
            boolean isSerp = (Integer)PrimitiveNode_getSpecialType.invoke(pn) == PrimitiveNode.SERPTRANS;
            int m = 0;
            for (Object nld: electricalNodeLayers) {
                int j = nodeLayers.indexOf(nld);
                if (j < 0) {
                    ng.nodeLayers.add(makeNodeLayerDetails(t, nld, isSerp, fullSize, false, true));
                    continue;
                }
                while (m < j)
                    ng.nodeLayers.add(makeNodeLayerDetails(t, nodeLayers.get(m++), isSerp, fullSize, true, false));
                ng.nodeLayers.add(makeNodeLayerDetails(t, nodeLayers.get(m++), isSerp, fullSize, true, true));
            }
            while (m < nodeLayers.size())
                ng.nodeLayers.add(makeNodeLayerDetails(t, nodeLayers.get(m++), isSerp, fullSize, true, false));

            for (Iterator<?> pit = (Iterator)PrimitiveNode_getPorts.invoke(pn); pit.hasNext(); ) {
                Object pp = pit.next();
                Xml.PrimitivePort ppd = new Xml.PrimitivePort();
                ppd.name = (String)PrimitivePort_getName.invoke(pp);
                ppd.portAngle = (Integer)PrimitivePort_getAngle.invoke(pp);
                if (PrimitivePort_getAngleRange != null) {
                    ppd.portRange = (Integer)PrimitivePort_getAngleRange.invoke(pp);
                } else {
                    /** range of valid angles about port angle */		final int PORTARANGE =           0377000;
                    /** right shift of PORTARANGE field */				final int PORTARANGESH =               9;
                    ppd.portRange = ((Integer)PrimitivePort_lowLevelGetUserbits.invoke(pp) & PORTARANGE) >> PORTARANGESH;
                }
                ppd.portTopology = (Integer)PrimitivePort_getTopology.invoke(pp);

                Object lx = PrimitivePort_getLeft.invoke(pp);
                Object hx = PrimitivePort_getRight.invoke(pp);
                Object ly = PrimitivePort_getBottom.invoke(pp);
                Object hy = PrimitivePort_getTop.invoke(pp);
                ppd.lx.k = (Double)EdgeH_getMultiplier.invoke(lx)*2;
                ppd.lx.addLambda(round((Double)EdgeH_getAdder.invoke(lx) + fullSize.getLambdaX()*ppd.lx.k));
                ppd.hx.k = (Double)EdgeH_getMultiplier.invoke(hx)*2;
                ppd.hx.addLambda(round((Double)EdgeH_getAdder.invoke(hx) + fullSize.getLambdaX()*ppd.hx.k));
                ppd.ly.k = (Double)EdgeV_getMultiplier.invoke(ly)*2;
                ppd.ly.addLambda(round((Double)EdgeV_getAdder.invoke(ly) + fullSize.getLambdaY()*ppd.ly.k));
                ppd.hy.k = (Double)EdgeV_getMultiplier.invoke(hy)*2;
                ppd.hy.addLambda(round((Double)EdgeV_getAdder.invoke(hy) + fullSize.getLambdaY()*ppd.hy.k));

                makePortArcs(ppd.portArcs, tech, pp, null);
                ng.ports.add(ppd);
            }
            ng.specialType = (Integer)PrimitiveNode_getSpecialType.invoke(pn);
            double[] specialValues = (double[])PrimitiveNode_getSpecialValues.invoke(pn);
            if (specialValues != null)
                ng.specialValues = specialValues.clone();
            if (PrimitiveNode_getSpiceTemplate != null)
                ng.spiceTemplate = (String)PrimitiveNode_getSpiceTemplate.invoke(pn);
            t.nodeGroups.add(ng);
        }

        addSpiceHeader(t, 1, (String[])Technology_getSpiceHeaderLevel1.invoke(tech));
        addSpiceHeader(t, 2, (String[])Technology_getSpiceHeaderLevel2.invoke(tech));
        addSpiceHeader(t, 3, (String[])Technology_getSpiceHeaderLevel3.invoke(tech));

        if (Technology_getNodesGrouped1 != null || Technology_getNodesGrouped2 != null) {
            Object[][] origPalette = null;
            if (Technology_getNodesGrouped1 != null)
                origPalette = (Object[][])Technology_getNodesGrouped1.invoke(tech);
            else if (Technology_getNodesGrouped2 != null)
                origPalette = (Object[][])Technology_getNodesGrouped2.invoke(tech, (Object)null);
            if (origPalette != null) {
                int numRows = origPalette.length;
                int numCols = origPalette[0].length;
                for (Object[] row: origPalette) {
                    assert row.length == numCols;
                }
                t.menuPalette = new Xml.MenuPalette();
                t.menuPalette.numColumns = numCols;
                for (int row = 0; row < numRows; row++) {
                    for (int col = 0; col < numCols; col++) {
                        Object origEntry = origPalette[row][col];
                        Object newEntry = null;
                        ArrayList<Object> newBox = new ArrayList<Object>();
                        if (origEntry instanceof List) {
                            List<?> list = (List<?>)origEntry;
                            for (Object o: list) {
                                if (o instanceof List) {
                                    List<?> list2 = (List<?>)o;
                                    for (Object o2: list2)
                                        newBox.add(makeMenuEntry(t, o2));
                                } else {
                                    newBox.add(makeMenuEntry(t, o));
                                }
                            }
                        } else if (origEntry != null) {
                            newBox.add(makeMenuEntry(t, origEntry));
                        }
                        t.menuPalette.menuBoxes.add(newBox);
                    }
                }
            }
        }
        makeFoundries(t, tech);

        return t;
    }

    private void makeFoundries(Xml.Technology t, Object tech) throws IllegalAccessException, InvocationTargetException {
        if (Technology_getFoundries == null) return;

        Object foundries = Technology_getFoundries.invoke(tech);
        Iterator<?> fit = foundries instanceof List ? ((List)foundries).iterator() : (Iterator)foundries;
        for (; fit.hasNext(); ) {
            Object foundry = fit.next();
            Xml.Foundry f = new Xml.Foundry();
            f.name = foundry.toString();
            if (Foundry_getGDSLayers != null) {
                Map<?,String> gdsMap = (Map)Foundry_getGDSLayers.invoke(foundry);
                for (Map.Entry<?,String> e: gdsMap.entrySet()) {
                    String gds = e.getValue();
                    if (gds.length() == 0) continue;
                    Object layer = e.getKey();
                    f.layerGds.put((String)Layer_getName.invoke(layer), gds);
                }
            }

            List<?> rules = (List)Foundry_getRules.invoke(foundry);
            if (rules != null) {
                for (Object rule: rules) {
                    String ruleName = (String)DRCTemplate_ruleName.get(rule);
                    int when = (Integer)DRCTemplate_when.get(rule);
                    final int TSMC = 010000;
                    final int ST = 020000;
                    final int MOSIS = 040000;
                    when = when & ~(TSMC|ST|MOSIS);
                    if (classDRCTemplateDRCMode != null) {
                        int newWhen = 0;
                        for (Map.Entry<Object,DRCTemplate.DRCMode> e: DRCTemplateDRCModes.entrySet()) {
                            int oldMode = (Integer)DRCTemplateDrcMode_mode.invoke(e.getKey());
                            if ((when & oldMode) == oldMode)
                                newWhen |= e.getValue().mode();
                        }
                        when = newWhen;
                    }
                    DRCTemplate.DRCRuleType type = DRCTemplateDRCRuleTypes.get(DRCTemplate_ruleType.get(rule));
                    if (type == null)
                        continue;
                    double maxWidth = (Double)DRCTemplate_maxWidth.get(rule);
                    double minLength = (Double)DRCTemplate_minLength.get(rule);
                    String name1 = (String)DRCTemplate_name1.get(rule);
                    String name2 = (String)DRCTemplate_name2.get(rule);
                    double[] values = null;
                    if (DRCTemplate_values != null) {
                        values = (double[])DRCTemplate_values.get(rule);
                    } else if (DRCTemplate_value1 != null & DRCTemplate_value2 != null) {
                        values = new double[2];
                        values[0] = (Double)DRCTemplate_value1.get(rule);
                        values[1] = (Double)DRCTemplate_value2.get(rule);
                    }
                    values = values.clone();
                    String nodeName = (String)DRCTemplate_nodeName.get(rule);
                    int multiCuts = (Integer)DRCTemplate_multiCuts.get(rule);
                    DRCTemplate r = null;
                    if (nodeName != null)
                        r = new DRCTemplate(ruleName, when, type, name1, name2, values, nodeName, null);
                    else
                        r = new DRCTemplate(ruleName, when, type, maxWidth, minLength, name1, name2, values, multiCuts);
                    f.rules.add(r);
                }
                t.foundries.add(f);
            }
        }

    }

    private Xml.NodeLayer makeNodeLayerDetails(Xml.Technology t, Object nodeLayer, boolean isSerp, EPoint correction, boolean inLayers, boolean inElectricalLayers)
    throws IllegalAccessException, InvocationTargetException {
        Xml.NodeLayer nld = new Xml.NodeLayer();
        Object layer = TechnologyNodeLayer_getLayer.invoke(nodeLayer);
        layer = Layer_getNonPseudoLayer.invoke(layer);
        nld.layer = (String)Layer_getName.invoke(layer);
        nld.style = PolyTypes.get(TechnologyNodeLayer_getStyle.invoke(nodeLayer));
        nld.portNum = (Integer)TechnologyNodeLayer_getPortNum.invoke(nodeLayer);
        nld.inLayers = inLayers;
        nld.inElectricalLayers = inElectricalLayers;
        nld.representation = (Integer)TechnologyNodeLayer_getRepresentation.invoke(nodeLayer);
        Object[] points = (Object[])TechnologyNodeLayer_getPoints.invoke(nodeLayer);
        if (points != null) {
            if ((nld.representation == Technology.NodeLayer.BOX || nld.representation == Technology.NodeLayer.MULTICUTBOX)) {
                Object lx = TechnologyTechPoint_getX.invoke(points[0]);
                Object hx = TechnologyTechPoint_getX.invoke(points[1]);
                Object ly = TechnologyTechPoint_getY.invoke(points[0]);
                Object hy = TechnologyTechPoint_getY.invoke(points[1]);
                nld.lx.k = (Double)EdgeH_getMultiplier.invoke(lx)*2;
                nld.lx.addLambda(round((Double)EdgeH_getAdder.invoke(lx) + correction.getLambdaX()*nld.lx.k));
                nld.hx.k = (Double)EdgeH_getMultiplier.invoke(hx)*2;
                nld.hx.addLambda(round((Double)EdgeH_getAdder.invoke(hx) + correction.getLambdaX()*nld.hx.k));
                nld.ly.k = (Double)EdgeV_getMultiplier.invoke(ly)*2;
                nld.ly.addLambda(round((Double)EdgeV_getAdder.invoke(ly) + correction.getLambdaY()*nld.ly.k));
                nld.hy.k = (Double)EdgeV_getMultiplier.invoke(hy)*2;
                nld.hy.addLambda(round((Double)EdgeV_getAdder.invoke(hy) + correction.getLambdaY()*nld.hy.k));
            } else {
                for (Object p: points)
                    nld.techPoints.add(correction(p, correction));
            }
        }
        if (TechnologyNodeLayer_getMulticutSizeX != null) {
            nld.sizex = round((Double)TechnologyNodeLayer_getMulticutSizeX.invoke(nodeLayer));
            nld.sizey = round((Double)TechnologyNodeLayer_getMulticutSizeY.invoke(nodeLayer));
            nld.sep1d = round((Double)TechnologyNodeLayer_getMulticutSep1D.invoke(nodeLayer));
            nld.sep2d = round((Double)TechnologyNodeLayer_getMulticutSep2D.invoke(nodeLayer));
        }
        if (isSerp) {
            nld.lWidth = round((Double)TechnologyNodeLayer_getSerpentineLWidth.invoke(nodeLayer));
            nld.rWidth = round((Double)TechnologyNodeLayer_getSerpentineRWidth.invoke(nodeLayer));
            nld.tExtent = round((Double)TechnologyNodeLayer_getSerpentineExtentT.invoke(nodeLayer));
            nld.bExtent = round((Double)TechnologyNodeLayer_getSerpentineExtentB.invoke(nodeLayer));
        }
        return nld;
    }

    private Technology.TechPoint correction(Object p, EPoint correction) throws IllegalAccessException, InvocationTargetException {
        Object oh = TechnologyTechPoint_getX.invoke(p);
        double mx = (Double)EdgeH_getMultiplier.invoke(oh);
        EdgeH h = new EdgeH(mx, (Double)EdgeH_getAdder.invoke(oh) + correction.getLambdaX()*mx*2);
        Object ov = TechnologyTechPoint_getY.invoke(p);
        double my = (Double)EdgeV_getMultiplier.invoke(ov);
        EdgeV v = new EdgeV(my, (Double)EdgeV_getAdder.invoke(ov) + correction.getLambdaY()*my*2);
        return new Technology.TechPoint(h, v);
    }

    private static void addSpiceHeader(Xml.Technology t, int level, String[] spiceLines) {
        if (spiceLines == null) return;
        Xml.SpiceHeader spiceHeader = new Xml.SpiceHeader();
        spiceHeader.level = level;
        for (String spiceLine: spiceLines)
            spiceHeader.spiceLines.add(spiceLine);
        t.spiceHeaders.add(spiceHeader);
    }

    private Object makeMenuEntry(Xml.Technology t, Object entry) throws IllegalAccessException, InvocationTargetException {
        if (classArcProto.isInstance(entry))
            return t.findArc((String)ArcProto_getName.invoke(entry));
        if (classPrimitiveNode.isInstance(entry)) {
            PrimitiveNode.Function fun = PrimitiveNodeFunctions.get(PrimitiveNode_getFunction.invoke(entry));
            String name = (String)PrimitiveNode_getName.invoke(entry);
//            if (fun == PrimitiveNode.Function.PIN) {
//                Xml.MenuNodeInst n = new Xml.MenuNodeInst();
//                n.protoName = name;
//                n.function = PrimitiveNode.Function.PIN;
//                return n;
//            }
            return t.findNode(name);
        }
        if (classNodeInst.isInstance(entry)) {
            Xml.MenuNodeInst n = new Xml.MenuNodeInst();
            n.protoName = (String)PrimitiveNode_getName.invoke(NodeInst_getProto.invoke(entry));
            n.function = PrimitiveNodeFunctions.get(NodeInst_getFunction.invoke(entry));
            n.rotation = (Integer)NodeInst_getAngle.invoke(entry);
            for (Iterator<?> it = (Iterator)ElectricObject_getVariables.invoke(entry); it.hasNext(); ) {
                Object var = it.next();
                Object value = Variable_getObject.invoke(var);
                if (!(value instanceof String)) continue;
                n.text = (String)Variable_getObject.invoke(var);
            }
            return n;
        }
        if (entry.getClass().getName().equals("javax.swing.JPopupMenu$Separator"))
            return Technology.SPECIALMENUSEPARATOR;
        assert entry instanceof String;
        return entry;
    }

    private void makePortArcs(List<String> portArcs, Object tech, Object pp, Object excludeAp) throws IllegalAccessException, InvocationTargetException  {
        Object[] connections = (Object[])PrimitivePort_getConnections.invoke(pp);
        for (Object ap: connections) {
            if (ap == null || ap == excludeAp) continue;
            String arcName = (String)ArcProto_getName.invoke(ap);
            if (Technology_findArcProto.invoke(tech, arcName) != ap) continue;
            portArcs.add(arcName);
        }
    }

    private boolean isPseudoLayer(Object layer) throws IllegalAccessException, InvocationTargetException {
        int extraFun = (Integer)Layer_getFunctionExtras.invoke(layer);
        final int PSEUDO =       010000;
        return (extraFun & PSEUDO) != 0 || Layer_isPseudoLayer != null && (Boolean)Layer_isPseudoLayer.invoke(layer);
    }

    private static double round(double v) {
        v = DBMath.round(v);
        return v;
    }

//    public Xml.Technology makeXml(String techName) throws IllegalAccessException, InvocationTargetException {
//        Object tech = Technology_findTechnology.invoke(null, techName);
//
//        Xml.Technology t = new Xml.Technology();
//        t.techName = techName;
//        t.className = tech.getClass().getName();
//        if (t.className.equals("com.sun.electric.technology.Technology"))
//            t.className = null;
//        t.shortTechName = (String)Technology_getTechShortName.invoke(tech);
//        t.description = (String)Technology_getTechDesc.invoke(tech);
//        t.scaleValue = (Double)Technology_getScale.invoke(tech);
//        t.scaleRelevant = (Boolean)Technology_isScaleRelevant.invoke(tech);
//        t.defaultFoundry = "NONE";
//        if (Technology_getPrefFoundry != null)
//            t.defaultFoundry = Technology_getPrefFoundry.invoke(tech).toString();
//        t.minResistance = (Double)Technology_getMinResistance.invoke(tech);
//        t.minCapacitance = (Double)Technology_getMinCapacitance.invoke(tech);
//        int numTransparentLayers = (Integer)Technology_getNumTransparentLayers.invoke(tech);
//        if (numTransparentLayers > 0) {
//            Color[] colorMap = (Color[])Technology_getColorMap.invoke(tech);
//            for (int i = 0; i < numTransparentLayers; i++) {
//                Color transparentColor = colorMap[1 << i];
//                t.transparentLayers.add(transparentColor);
//            }
//        }
//        makeFoundries(t, tech);
//
//        int maxMetal = 0;
//        for (Iterator<?> it = (Iterator)Technology_getLayers.invoke(tech); it.hasNext(); ) {
//            Object layer = it.next();
//            if (isPseudoLayer(layer)) continue;
//            String layerName = (String)Layer_getName.invoke(layer);
//
//            Xml.Layer l = new Xml.Layer();
//            l.name = layerName;
//            Object fun = Layer_getFunction.invoke(layer);
//            l.function = fun != null ? LayerFunctions.get(fun) : Layer.Function.UNKNOWN;
//            if (l.function.isMetal())
//                maxMetal = Math.max(maxMetal, l.function.getLevel());
//            l.extraFunction = (Integer)Layer_getFunctionExtras.invoke(layer);
//            Object desc = Layer_getGraphics.invoke(layer);
//            boolean displayPatterned = (Boolean)EGraphics_isPatternedOnDisplay.invoke(desc);
//            boolean printPatterned = (Boolean)EGraphics_isPatternedOnPrinter.invoke(desc);
//            EGraphics.Outline outlineWhenPatterned = EGraphics.Outline.NOPAT;
//            if (EGraphics_getOutlined != null) {
//                Object outline = EGraphics_getOutlined.invoke(desc);
//                if (outline != null)
//                    outlineWhenPatterned = EGraphicsOutlines.get(outline);
//            } else {
//                if (EGraphics_isOutlinedOnDisplay != null && (Boolean)EGraphics_isOutlinedOnDisplay.invoke(desc))
//                    outlineWhenPatterned = EGraphics.Outline.PAT_S;
//                if (EGraphics_isOutlinedOnPrinter != null && (Boolean)EGraphics_isOutlinedOnPrinter.invoke(desc))
//                    outlineWhenPatterned = EGraphics.Outline.PAT_S;
//            }
//            int transparentLayer = (Integer)EGraphics_getTransparentLayer.invoke(desc);
//            Color color = (Color)EGraphics_getColor.invoke(desc);
//            double opacity = (Double)EGraphics_getOpacity.invoke(desc);
//            boolean foreground = (Boolean)EGraphics_getForeground.invoke(desc);
//            int[] pattern = (int[])EGraphics_getPattern.invoke(desc);
//            l.desc = new EGraphics(displayPatterned, printPatterned, outlineWhenPatterned,
//                    transparentLayer, color.getRed(), color.getGreen(), color.getBlue(), opacity, foreground, pattern);
//            l.thick3D = (Double)Layer_getThickness.invoke(layer);
//            if (Layer_getDistance != null)
//                l.height3D = (Double)Layer_getDistance.invoke(layer);
//            else if (Layer_getHeight != null)
//                l.height3D = (Double)Layer_getHeight.invoke(layer);
//            if (Layer_getTransparencyMode != null)
//                l.mode3D = (String)Layer_getTransparencyMode.invoke(layer);
//            if (Layer_getTransparencyFactor != null)
//                l.factor3D = (Double)Layer_getTransparencyFactor.invoke(layer);
//            l.cif = (String)Layer_getCIFLayer.invoke(layer);
//            l.skill = (String)Layer_getSkillLayer.invoke(layer);
//            l.resistance = (Double)Layer_getResistance.invoke(layer);
//            l.capacitance = (Double)Layer_getCapacitance.invoke(layer);
//            l.edgeCapacitance = (Double)Layer_getEdgeCapacitance.invoke(layer);
//            t.layers.add(l);
//        }
//        if (Technology_getNumMetals != null)
//            maxMetal = (Integer)Technology_getNumMetals.invoke(tech);
//        t.minNumMetals = t.maxNumMetals = t.defaultNumMetals = maxMetal;
//
//        HashSet<Object> arcPins = new HashSet<Object>();
//        Map<String,?> oldArcNames = Technology_getOldArcNames != null ? (Map)Technology_getOldArcNames.invoke(tech) : Collections.emptyMap();
//        for (Iterator<?> it = (Iterator)Technology_getArcs.invoke(tech); it.hasNext(); ) {
//            Object ap = it.next();
//            String arcName = (String)ArcProto_getName.invoke(ap);
//
//            Xml.ArcProto a = new Xml.ArcProto();
//            a.name = arcName;
//            for (Map.Entry<String,?> e: oldArcNames.entrySet()) {
//                if (e.getValue() == ap)
//                    a.oldName = e.getKey();
//            }
//            a.function = ArcProtoFunctions.get(ArcProto_getFunction.invoke(ap));
//            a.wipable = (Boolean)ArcProto_isWipable.invoke(ap);
//            a.curvable = (Boolean)ArcProto_isCurvable.invoke(ap);
//            a.special = ArcProto_isSpecialArc != null && (Boolean)ArcProto_isSpecialArc.invoke(ap);
//            a.skipSizeInPalette = ArcProto_isSkipSizeInPalette != null && (Boolean)ArcProto_isSkipSizeInPalette.invoke(ap);
//            a.notUsed = (Boolean)ArcProto_isNotUsed.invoke(ap);
//            a.extended = (Boolean)ArcProto_isExtended.invoke(ap);
//            a.fixedAngle = (Boolean)ArcProto_isFixedAngle.invoke(ap);
//            a.angleIncrement = (Integer)ArcProto_getAngleIncrement.invoke(ap);
//            if (ERC_getAntennaRatio != null)
//                a.antennaRatio = (Double)ERC_getAntennaRatio.invoke(ERC_tool.get(null), ap);
//            else if (ArcProto_getAntennaRatio != null)
//                a.antennaRatio = (Double)ArcProto_getAntennaRatio.invoke(ap);
//            double defaultFullWidth = 0;
//            if (ArcProto_getDefaultLambdaFullWidth != null)
//                defaultFullWidth = (Double)ArcProto_getDefaultLambdaFullWidth.invoke(ap);
//            else if (ArcProto_getDefaultWidth != null)
//                defaultFullWidth = (Double)ArcProto_getDefaultWidth.invoke(ap);
//            double widthOffset = 0;
//            if (ArcProto_getLambdaElibWidthOffset != null)
//                widthOffset = (Double)ArcProto_getLambdaElibWidthOffset.invoke(ap);
//            else if (ArcProto_getLambdaWidthOffset != null)
//                widthOffset = (Double)ArcProto_getLambdaWidthOffset.invoke(ap);
//            else if (ArcProto_getWidthOffset != null)
//                widthOffset = (Double)ArcProto_getWidthOffset.invoke(ap);
//            a.elibWidthOffset = round(widthOffset);
//            Object[] arcLayers = (Object[])ArcProto_layers.get(ap);
//            for (Object arcLayer: arcLayers) {
//                Xml.ArcLayer al = new Xml.ArcLayer();
//                al.layer = (String)Layer_getName.invoke(TechnologyArcLayer_getLayer.invoke(arcLayer));
//                al.style = PolyTypes.get(TechnologyArcLayer_getStyle.invoke(arcLayer));
//                double extend = 0;
//                if (TechnologyArcLayer_getGridExtend != null) {
//                    extend = DBMath.gridToLambda((Integer)TechnologyArcLayer_getGridExtend.invoke(arcLayer));
//                } else {
//                    double offset = 0;
//                    if (TechnologyArcLayer_getLambdaOffset != null)
//                        offset = (Double)TechnologyArcLayer_getLambdaOffset.invoke(arcLayer);
//                    else if (TechnologyArcLayer_getOffset != null)
//                        offset = (Double)TechnologyArcLayer_getOffset.invoke(arcLayer);
//                    extend = 0.5*(defaultFullWidth - offset);
//                }
//                al.extend.addLambda(round(extend));
//                a.arcLayers.add(al);
//            }
//            t.arcs.add(a);
//            a.arcPin = makeWipablePin(tech, ap, arcPins);
//        }
//
//        Map<String,?> oldNodeNames = Technology_getOldNodeNames != null ? (Map)Technology_getOldNodeNames.invoke(tech) : Collections.emptyMap();
//        for (Iterator<?> it = (Iterator)Technology_getNodes.invoke(tech); it.hasNext(); ) {
//            Object pn = it.next();
//            if (arcPins.contains(pn)) continue;
//            String nodeName = (String)PrimitiveNode_getName.invoke(pn);
//            PrimitiveNode.Function fun = PrimitiveNodeFunctions.get(PrimitiveNode_getFunction.invoke(pn));
//            Object[] nodeLayersArray = (Object[])PrimitiveNode_getLayers.invoke(pn);
//            double defWidth = (Double)PrimitiveNode_getDefWidth.invoke(pn);
//            double defHeight = (Double)PrimitiveNode_getDefHeight.invoke(pn);
//            Iterator<?> ports = (Iterator)PrimitiveNode_getPorts.invoke(pn);
//            if (fun == PrimitiveNode.Function.NODE && nodeLayersArray.length == 1) {
//                Xml.PureLayerNode pln = new Xml.PureLayerNode();
//                pln.name = nodeName;
//                for (Map.Entry<String,?> e: oldNodeNames.entrySet()) {
//                    if (e.getValue() == pn)
//                        pln.oldName = e.getKey();
//                }
//                Object port = ports.next();
//                pln.port = (String)PrimitivePort_getName.invoke(port);
//                pln.style = PolyTypes.get(TechnologyNodeLayer_getStyle.invoke(nodeLayersArray[0]));
//                pln.size.addLambda(round(defWidth));
//                makePortArcs(pln.portArcs, tech, port, null);
//                Xml.Layer layer = t.findLayer((String)Layer_getName.invoke(TechnologyNodeLayer_getLayer.invoke(nodeLayersArray[0])));
//                layer.pureLayerNode = pln;
//                continue;
//            }
//
//            Xml.PrimitiveNode n = new Xml.PrimitiveNode();
//            n.name = nodeName;
//            for (Map.Entry<String,?> e: oldNodeNames.entrySet()) {
//                if (e.getValue() == pn)
//                    n.oldName = e.getKey();
//            }
//            n.function = fun;
//            n.shrinkArcs = (Boolean)PrimitiveNode_isArcsShrink.invoke(pn);
//            n.square = (Boolean)PrimitiveNode_isSquare.invoke(pn);
//            n.canBeZeroSize = (Boolean)PrimitiveNode_isCanBeZeroSize.invoke(pn);
//            n.wipes = (Boolean)PrimitiveNode_isWipeOn1or2.invoke(pn);
//            n.lockable = (Boolean)PrimitiveNode_isLockedPrim.invoke(pn);
//            n.edgeSelect = (Boolean)PrimitiveNode_isEdgeSelect.invoke(pn);
//            if (PrimitiveNode_isSkipSizeInPalette != null)
//                n.skipSizeInPalette = (Boolean)PrimitiveNode_isSkipSizeInPalette.invoke(pn);
//            n.notUsed = (Boolean)PrimitiveNode_isNotUsed.invoke(pn);
//            if (PrimitiveNode_LOWVTBIT != null)
//                n.lowVt = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_LOWVTBIT.get(null));
//            if (PrimitiveNode_HIGHVTBIT != null)
//                n.highVt = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_HIGHVTBIT.get(null));
//            if (PrimitiveNode_NATIVEBIT != null)
//                n.nativeBit = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_NATIVEBIT.get(null));
//            if (PrimitiveNode_OD18BIT != null)
//                n.od18 = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_OD18BIT.get(null));
//            if (PrimitiveNode_OD25BIT != null)
//                n.od25 = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_OD25BIT.get(null));
//            if (PrimitiveNode_OD33BIT != null)
//                n.od33 = (Boolean)PrimitiveNode_isNodeBitOn.invoke(pn, PrimitiveNode_OD33BIT.get(null));
//
//            double minWidth = 0, minHeight = 0;
//            String minSizeRule = null;
//            if (classPrimitiveNodeNodeSizeRule != null) {
//                Object rule = PrimitiveNode_getMinSizeRule.invoke(pn);
//                if (rule != null) {
//                    minWidth = (Double)PrimitiveNodeNodeSizeRule_getWidth.invoke(rule);
//                    minHeight = (Double)PrimitiveNodeNodeSizeRule_getHeight.invoke(rule);
//                    minSizeRule = (String)PrimitiveNodeNodeSizeRule_getRuleName.invoke(rule);
//                }
//            } else {
//                minWidth = (Double)PrimitiveNode_getMinWidth.invoke(pn);
//                minHeight = (Double)PrimitiveNode_getMinHeight.invoke(pn);
//                minSizeRule = (String)PrimitiveNode_getMinSizeRule.invoke(pn);
//                if (minWidth == -1 && minHeight == -1 && minSizeRule.equals(""))
//                    minSizeRule = null;
//            }
//            EPoint minFullSize;
//            if (minSizeRule != null) {
//                n.nodeSizeRule = new Xml.NodeSizeRule();
//                n.nodeSizeRule.width = minWidth;
//                n.nodeSizeRule.height = minHeight;
//                n.nodeSizeRule.rule = minSizeRule;
//                minFullSize = EPoint.fromLambda(0.5*minWidth, 0.5*minHeight);
//            } else {
//                minFullSize = EPoint.fromLambda(0.5*defWidth, 0.5*defHeight);
//            }
//            n.spiceTemplate = null; // ??????????
//
//            ERectangle nodeBase;
//            if (PrimitiveNode_getBaseRectangle != null) {
//                Rectangle2D baseRectangle = (Rectangle2D)PrimitiveNode_getBaseRectangle.invoke(pn);
//                nodeBase = ERectangle.fromLambda(baseRectangle);
//            } else {
//                double lx = -minFullSize.getLambdaX();
//                double hx = minFullSize.getLambdaX();
//                double ly = -minFullSize.getLambdaY();
//                double hy = minFullSize.getLambdaY();
//                Object sizeOffset = PrimitiveNode_getProtoSizeOffset.invoke(pn);
//                if (sizeOffset != null) {
//                    lx += (Double)SizeOffset_getLowXOffset.invoke(sizeOffset);
//                    hx -= (Double)SizeOffset_getHighXOffset.invoke(sizeOffset);
//                    ly += (Double)SizeOffset_getLowYOffset.invoke(sizeOffset);
//                    hy -= (Double)SizeOffset_getHighYOffset.invoke(sizeOffset);
//                }
//                nodeBase = ERectangle.fromLambda(lx, ly, hx - lx, hy - ly);
//            }
//            n.nodeBase = nodeBase;
//            if (!minFullSize.equals(EPoint.ORIGIN))
//                n.diskOffset = minFullSize;
////            EPoint p2 = EPoint.fromGrid(nodeBase.getGridWidth() >> 1, nodeBase.getGridHeight() >> 1);
////            if (!p2.equals(minFullSize))
////                n.diskOffset.put(Integer.valueOf(1), minFullSize);
////            if (!p2.equals(EPoint.ORIGIN))
////                n.diskOffset.put(Integer.valueOf(2), p2);
//            n.defaultWidth.addLambda(round(defWidth - 2*minFullSize.getLambdaX()));
//            n.defaultHeight.addLambda(round(defHeight - 2*minFullSize.getLambdaY()));
//
//            List<?> nodeLayers = Arrays.asList(nodeLayersArray);
//            Object[] electricalNodeLayersArray = (Object[])PrimitiveNode_getElectricalLayers.invoke(pn);
//            List<?> electricalNodeLayers = nodeLayers;
//            if (electricalNodeLayersArray != null)
//                electricalNodeLayers = Arrays.asList(electricalNodeLayersArray);
//            boolean isSerp = (Integer)PrimitiveNode_getSpecialType.invoke(pn) == PrimitiveNode.SERPTRANS;
//            int m = 0;
//            for (Object nld: electricalNodeLayers) {
//                int j = nodeLayers.indexOf(nld);
//                if (j < 0) {
//                    n.nodeLayers.add(makeNodeLayerDetails(t, nld, isSerp, minFullSize, false, true));
//                    continue;
//                }
//                while (m < j)
//                    n.nodeLayers.add(makeNodeLayerDetails(t, nodeLayers.get(m++), isSerp, minFullSize, true, false));
//                n.nodeLayers.add(makeNodeLayerDetails(t, nodeLayers.get(m++), isSerp, minFullSize, true, true));
//            }
//            while (m < nodeLayers.size())
//                n.nodeLayers.add(makeNodeLayerDetails(t, nodeLayers.get(m++), isSerp, minFullSize, true, false));
//
//            for (Iterator<?> pit = (Iterator)PrimitiveNode_getPorts.invoke(pn); pit.hasNext(); ) {
//                Object pp = pit.next();
//                Xml.PrimitivePort ppd = new Xml.PrimitivePort();
//                ppd.name = (String)PrimitivePort_getName.invoke(pp);
//                ppd.portAngle = (Integer)PrimitivePort_getAngle.invoke(pp);
//                if (PrimitivePort_getAngleRange != null) {
//                    ppd.portRange = (Integer)PrimitivePort_getAngleRange.invoke(pp);
//                } else {
//                    /** range of valid angles about port angle */		final int PORTARANGE =           0377000;
//                    /** right shift of PORTARANGE field */				final int PORTARANGESH =               9;
//                    ppd.portRange = ((Integer)PrimitivePort_lowLevelGetUserbits.invoke(pp) & PORTARANGE) >> PORTARANGESH;
//                }
//                ppd.portTopology = (Integer)PrimitivePort_getTopology.invoke(pp);
//
//                Object lx = PrimitivePort_getLeft.invoke(pp);
//                Object hx = PrimitivePort_getRight.invoke(pp);
//                Object ly = PrimitivePort_getBottom.invoke(pp);
//                Object hy = PrimitivePort_getTop.invoke(pp);
//                ppd.lx.k = (Double)EdgeH_getMultiplier.invoke(lx)*2;
//                ppd.lx.addLambda(round((Double)EdgeH_getAdder.invoke(lx) + minFullSize.getLambdaX()*ppd.lx.k));
//                ppd.hx.k = (Double)EdgeH_getMultiplier.invoke(hx)*2;
//                ppd.hx.addLambda(round((Double)EdgeH_getAdder.invoke(hx) + minFullSize.getLambdaX()*ppd.hx.k));
//                ppd.ly.k = (Double)EdgeV_getMultiplier.invoke(ly)*2;
//                ppd.ly.addLambda(round((Double)EdgeV_getAdder.invoke(ly) + minFullSize.getLambdaY()*ppd.ly.k));
//                ppd.hy.k = (Double)EdgeV_getMultiplier.invoke(hy)*2;
//                ppd.hy.addLambda(round((Double)EdgeV_getAdder.invoke(hy) + minFullSize.getLambdaY()*ppd.hy.k));
//
//                makePortArcs(ppd.portArcs, tech, pp, null);
//                n.ports.add(ppd);
//            }
//            n.specialType = (Integer)PrimitiveNode_getSpecialType.invoke(pn);
//            double[] specialValues = (double[])PrimitiveNode_getSpecialValues.invoke(pn);
//            if (specialValues != null)
//                n.specialValues = specialValues.clone();
//            t.nodes.add(n);
//        }
//
//        addSpiceHeader(t, 1, (String[])Technology_getSpiceHeaderLevel1.invoke(tech));
//        addSpiceHeader(t, 2, (String[])Technology_getSpiceHeaderLevel2.invoke(tech));
//        addSpiceHeader(t, 3, (String[])Technology_getSpiceHeaderLevel3.invoke(tech));
//
//        if (Technology_getNodesGrouped1 != null || Technology_getNodesGrouped2 != null) {
//            Object[][] origPalette = null;
//            if (Technology_getNodesGrouped1 != null)
//                origPalette = (Object[][])Technology_getNodesGrouped1.invoke(tech);
//            else if (Technology_getNodesGrouped2 != null)
//                origPalette = (Object[][])Technology_getNodesGrouped2.invoke(tech, (Object)null);
//            if (origPalette != null) {
//                int numRows = origPalette.length;
//                int numCols = origPalette[0].length;
//                for (Object[] row: origPalette) {
//                    assert row.length == numCols;
//                }
//                t.menuPalette = new Xml.MenuPalette();
//                t.menuPalette.numColumns = numCols;
//                for (int row = 0; row < numRows; row++) {
//                    for (int col = 0; col < numCols; col++) {
//                        Object origEntry = origPalette[row][col];
//                        Object newEntry = null;
//                        ArrayList<Object> newBox = new ArrayList<Object>();
//                        if (origEntry instanceof List) {
//                            List<?> list = (List<?>)origEntry;
//                            for (Object o: list) {
//                                if (o instanceof List) {
//                                    List<?> list2 = (List<?>)o;
//                                    for (Object o2: list2)
//                                        newBox.add(makeMenuEntry(t, o2));
//                                } else {
//                                    newBox.add(makeMenuEntry(t, o));
//                                }
//                            }
//                        } else if (origEntry != null) {
//                            newBox.add(makeMenuEntry(t, origEntry));
//                        }
//                        t.menuPalette.menuBoxes.add(newBox);
//                    }
//                }
//            }
//        }
//
//        return t;
//    }
//
//    private void makeFoundries(Xml.Technology t, Object tech) throws IllegalAccessException, InvocationTargetException {
//        if (Technology_getFoundries == null) return;
//
//        Object foundries = Technology_getFoundries.invoke(tech);
//        Iterator<?> fit = foundries instanceof List ? ((List)foundries).iterator() : (Iterator)foundries;
//        for (; fit.hasNext(); ) {
//            Object foundry = fit.next();
//            Xml.Foundry f = new Xml.Foundry();
//            f.name = foundry.toString();
//            if (Foundry_getGDSLayers != null) {
//                Map<?,String> gdsMap = (Map)Foundry_getGDSLayers.invoke(foundry);
//                for (Map.Entry<?,String> e: gdsMap.entrySet()) {
//                    String gds = e.getValue();
//                    if (gds.length() == 0) continue;
//                    Object layer = e.getKey();
//                    f.layerGds.put((String)Layer_getName.invoke(layer), gds);
//                }
//            }
//
//            List<?> rules = (List)Foundry_getRules.invoke(foundry);
//            if (rules != null) {
//                for (Object rule: rules) {
//                    String ruleName = (String)DRCTemplate_ruleName.get(rule);
//                    int when = (Integer)DRCTemplate_when.get(rule);
//                    final int TSMC = 010000;
//                    final int ST = 020000;
//                    final int MOSIS = 040000;
//                    when = when & ~(TSMC|ST|MOSIS);
//                    if (classDRCTemplateDRCMode != null) {
//                        int newWhen = 0;
//                        for (Map.Entry<Object,DRCTemplate.DRCMode> e: DRCTemplateDRCModes.entrySet()) {
//                            int oldMode = (Integer)DRCTemplateDrcMode_mode.invoke(e.getKey());
//                            if ((when & oldMode) == oldMode)
//                                newWhen |= e.getValue().mode();
//                        }
//                        when = newWhen;
//                    }
//                    DRCTemplate.DRCRuleType type = DRCTemplateDRCRuleTypes.get(DRCTemplate_ruleType.get(rule));
//                    if (type == null)
//                        continue;
//                    double maxWidth = (Double)DRCTemplate_maxWidth.get(rule);
//                    double minLength = (Double)DRCTemplate_minLength.get(rule);
//                    String name1 = (String)DRCTemplate_name1.get(rule);
//                    String name2 = (String)DRCTemplate_name2.get(rule);
//                    double[] values = null;
//                    if (DRCTemplate_values != null) {
//                        values = (double[])DRCTemplate_values.get(rule);
//                    } else if (DRCTemplate_value1 != null & DRCTemplate_value2 != null) {
//                        values = new double[2];
//                        values[0] = (Double)DRCTemplate_value1.get(rule);
//                        values[1] = (Double)DRCTemplate_value2.get(rule);
//                    }
//                    values = values.clone();
//                    String nodeName = (String)DRCTemplate_nodeName.get(rule);
//                    int multiCuts = (Integer)DRCTemplate_multiCuts.get(rule);
//                    DRCTemplate r = null;
//                    if (nodeName != null)
//                        r = new DRCTemplate(ruleName, when, type, name1, name2, values, nodeName, null);
//                    else
//                        r = new DRCTemplate(ruleName, when, type, maxWidth, minLength, name1, name2, values, multiCuts);
//                    f.rules.add(r);
//                }
//                t.foundries.add(f);
//            }
//        }
//
//    }
//
//    private Xml.ArcPin makeWipablePin(Object tech, Object ap, HashSet<Object> arcPins) throws IllegalAccessException, InvocationTargetException {
//        for (Iterator<?> it = (Iterator)Technology_getNodes.invoke(tech); it.hasNext(); ) {
//            Object pn = it.next();
//            PrimitiveNode.Function fun = PrimitiveNodeFunctions.get(PrimitiveNode_getFunction.invoke(pn));
//            if (fun != PrimitiveNode.Function.PIN) continue;
//
//            // Single port
//            Iterator<?> ports = (Iterator)PrimitiveNode_getPorts.invoke(pn);
//            if (!ports.hasNext()) continue;
//            Object pp = ports.next();
//            if (ports.hasNext()) continue;
//            Object[] connections = (Object[])PrimitivePort_getConnections.invoke(pp);
//            if (connections.length == 0 || connections[0] != ap) continue;
//
//            // All layers are pseudo layers
//            Object[] nodeLayersArray = (Object[])PrimitiveNode_getLayers.invoke(pn);
//            boolean allPseudo = true;
//            for (Object nld: nodeLayersArray) {
//                boolean isPseudo;
//                if (TechnologyNodeLayer_isPseudoLayer != null)
//                    isPseudo = (Boolean)TechnologyNodeLayer_isPseudoLayer.invoke(nld);
//                else
//                    isPseudo = isPseudoLayer(TechnologyNodeLayer_getLayer.invoke(nld));
//                allPseudo = allPseudo && isPseudo;
//            }
//            if (!allPseudo) continue;
//
//            // Square geometry
//            Object lx = PrimitivePort_getLeft.invoke(pp);
//            Object hx = PrimitivePort_getRight.invoke(pp);
//            Object ly = PrimitivePort_getBottom.invoke(pp);
//            Object hy = PrimitivePort_getTop.invoke(pp);
//            if ((Double)EdgeH_getMultiplier.invoke(lx) != -0.5 || (Double)EdgeH_getMultiplier.invoke(hx) != 0.5) continue;
//            if ((Double)EdgeV_getMultiplier.invoke(ly) != -0.5 || (Double)EdgeV_getMultiplier.invoke(hy) != 0.5) continue;
//            double portOffset = round((Double)EdgeH_getAdder.invoke(lx));
//            if (round((Double)EdgeH_getAdder.invoke(hx)) != -portOffset) continue;
//            if (round((Double)EdgeV_getAdder.invoke(ly)) != portOffset) continue;
//            if (round((Double)EdgeV_getAdder.invoke(hy)) != -portOffset) continue;
//
//            Xml.ArcPin arcPin = new Xml.ArcPin();
//            arcPin.name = (String)PrimitiveNode_getName.invoke(pn);
//            arcPin.portName = (String)PrimitivePort_getName.invoke(pp);
//            double arcPinElibSize = 2*portOffset;
//            if (PrimitiveNode_getSizeCorrector != null && PrimitiveNode_getFullRectangle != null) {
//                Point2D sizeCorrector = (Point2D)PrimitiveNode_getSizeCorrector.invoke(pn, 0);
//                Rectangle2D fullRectangle = (Rectangle2D)PrimitiveNode_getFullRectangle.invoke(pn);
//                arcPinElibSize += 2*sizeCorrector.getX() - fullRectangle.getWidth();
//            }
//            arcPin.elibSize = DBMath.round(arcPinElibSize);
//            makePortArcs(arcPin.portArcs, tech, pp, ap);
//            arcPins.add(pn);
//            return arcPin;
//        }
//        return null;
//    }
//
//    private Xml.NodeLayer makeNodeLayerDetails(Xml.Technology t, Object nodeLayer, boolean isSerp, EPoint correction, boolean inLayers, boolean inElectricalLayers)
//    throws IllegalAccessException, InvocationTargetException {
//        Xml.NodeLayer nld = new Xml.NodeLayer();
//        Object layer = TechnologyNodeLayer_getLayer.invoke(nodeLayer);
//        layer = Layer_getNonPseudoLayer.invoke(layer);
//        nld.layer = (String)Layer_getName.invoke(layer);
//        nld.style = PolyTypes.get(TechnologyNodeLayer_getStyle.invoke(nodeLayer));
//        nld.portNum = (Integer)TechnologyNodeLayer_getPortNum.invoke(nodeLayer);
//        nld.inLayers = inLayers;
//        nld.inElectricalLayers = inElectricalLayers;
//        nld.representation = (Integer)TechnologyNodeLayer_getRepresentation.invoke(nodeLayer);
//        Object[] points = (Object[])TechnologyNodeLayer_getPoints.invoke(nodeLayer);
//        if (points != null) {
//            if ((nld.representation == Technology.NodeLayer.BOX || nld.representation == Technology.NodeLayer.MULTICUTBOX)) {
//                Object lx = TechnologyTechPoint_getX.invoke(points[0]);
//                Object hx = TechnologyTechPoint_getX.invoke(points[1]);
//                Object ly = TechnologyTechPoint_getY.invoke(points[0]);
//                Object hy = TechnologyTechPoint_getY.invoke(points[1]);
//                nld.lx.k = (Double)EdgeH_getMultiplier.invoke(lx)*2;
//                nld.lx.addLambda(round((Double)EdgeH_getAdder.invoke(lx) + correction.getLambdaX()*nld.lx.k));
//                nld.hx.k = (Double)EdgeH_getMultiplier.invoke(hx)*2;
//                nld.hx.addLambda(round((Double)EdgeH_getAdder.invoke(hx) + correction.getLambdaX()*nld.hx.k));
//                nld.ly.k = (Double)EdgeV_getMultiplier.invoke(ly)*2;
//                nld.ly.addLambda(round((Double)EdgeV_getAdder.invoke(ly) + correction.getLambdaY()*nld.ly.k));
//                nld.hy.k = (Double)EdgeV_getMultiplier.invoke(hy)*2;
//                nld.hy.addLambda(round((Double)EdgeV_getAdder.invoke(hy) + correction.getLambdaY()*nld.hy.k));
//                if (nld.representation == Technology.NodeLayer.MULTICUTBOX) {
//                    DRCTemplate sizeRule = findLayerRule(t, nld.layer, DRCTemplate.DRCRuleType.MINWID);
//                    if (sizeRule == null) {
//                        double value = round((Double)TechnologyNodeLayer_getMulticutSizeX.invoke(nodeLayer));
//                        sizeRule = makeLayerRule(t, "W_" + nld.layer, nld.layer, DRCTemplate.DRCRuleType.MINWID, value);
//                    }
//                    nld.sizeRule = makeRuleName(sizeRule);
//
//                    DRCTemplate sepRule = findLayersRule(t, nld.layer, nld.layer, DRCTemplate.DRCRuleType.CONSPA);
//                    if (sepRule == null)
//                        sepRule = findLayersRule(t, nld.layer, nld.layer, DRCTemplate.DRCRuleType.SPACING);
//                    if (sepRule == null)
//                        sepRule = findLayersRule(t, nld.layer, nld.layer, DRCTemplate.DRCRuleType.UCONSPA);
//                    if (sepRule == null) {
//                        double value = round((Double)TechnologyNodeLayer_getMulticutSep2D.invoke(nodeLayer));
//                        sepRule = makeLayersRule(t, "C_" + nld.layer + "_" + nld.layer, nld.layer, nld.layer, DRCTemplate.DRCRuleType.CONSPA, value);
//                    }
//                    nld.sepRule = makeRuleName(sepRule);
//
//                    DRCTemplate sepRule2D = findLayersRule(t, nld.layer, nld.layer, DRCTemplate.DRCRuleType.UCONSPA2D);
//                    if (sepRule2D != null)
//                        nld.sepRule2D = makeRuleName(sepRule2D);
//                }
//            } else {
//                for (Object p: points)
//                    nld.techPoints.add(correction(p, correction));
//            }
//        }
//        if (isSerp) {
//            nld.lWidth = round((Double)TechnologyNodeLayer_getSerpentineLWidth.invoke(nodeLayer));
//            nld.rWidth = round((Double)TechnologyNodeLayer_getSerpentineRWidth.invoke(nodeLayer));
//            nld.tExtent = round((Double)TechnologyNodeLayer_getSerpentineExtentT.invoke(nodeLayer));
//            nld.bExtent = round((Double)TechnologyNodeLayer_getSerpentineExtentB.invoke(nodeLayer));
//        }
//        return nld;
//    }
//
//    private DRCTemplate findLayersRule(Xml.Technology t, String layerName1, String layerName2, DRCTemplate.DRCRuleType ruleType) {
//        Xml.Foundry foundry = t.foundries.get(0);
//        for (DRCTemplate rule: foundry.rules) {
//            if (rule.ruleType == ruleType && rule.name1.equals(layerName1) && rule.name2.equals(layerName2))
//                return rule;
//        }
//        return null;
//    }
//
//    private DRCTemplate findLayerRule(Xml.Technology t, String layerName, DRCTemplate.DRCRuleType ruleType) {
//        Xml.Foundry foundry = t.foundries.get(0);
//        for (DRCTemplate rule: foundry.rules) {
//            if (rule.ruleType == ruleType && rule.name1.equals(layerName))
//                return rule;
//        }
//        return null;
//    }
//
//    private DRCTemplate makeLayersRule(Xml.Technology t, String ruleName, String layerName1, String layerName2, DRCTemplate.DRCRuleType ruleType, double value) {
//        DRCTemplate rule = null;
//        for (Xml.Foundry foundry: t.foundries) {
//            rule = new DRCTemplate(ruleName, DRCTemplate.DRCMode.ALL.mode(), ruleType,
//                    layerName1, layerName2, new double[] {value}, null, null);
//            foundry.rules.add(rule);
//        }
//        return rule;
//    }
//
//    private DRCTemplate makeLayerRule(Xml.Technology t, String ruleName, String layerName, DRCTemplate.DRCRuleType ruleType, double value) {
//        DRCTemplate rule = null;
//        for (Xml.Foundry foundry: t.foundries) {
//            rule = new DRCTemplate(ruleName, DRCTemplate.DRCMode.ALL.mode(), ruleType,
//                    layerName, null, new double[] {value}, null, null);
//            foundry.rules.add(rule);
//        }
//        return rule;
//    }
//
//    private String makeRuleName(DRCTemplate rule) {
//        String ruleName = rule.ruleName;
//        int spaceIndex = ruleName.indexOf(' ');
//        if (spaceIndex >= 0)
//            ruleName = ruleName.substring(0, spaceIndex);
//        return ruleName;
//    }
//
//    private Technology.TechPoint correction(Object p, EPoint correction) throws IllegalAccessException, InvocationTargetException {
//        Object oh = TechnologyTechPoint_getX.invoke(p);
//        double mx = (Double)EdgeH_getMultiplier.invoke(oh);
//        EdgeH h = new EdgeH(mx, (Double)EdgeH_getAdder.invoke(oh) + correction.getLambdaX()*mx*2);
//        Object ov = TechnologyTechPoint_getY.invoke(p);
//        double my = (Double)EdgeV_getMultiplier.invoke(ov);
//        EdgeV v = new EdgeV(my, (Double)EdgeV_getAdder.invoke(ov) + correction.getLambdaY()*my*2);
//        return new Technology.TechPoint(h, v);
//    }
//
//    private static void addSpiceHeader(Xml.Technology t, int level, String[] spiceLines) {
//        if (spiceLines == null) return;
//        Xml.SpiceHeader spiceHeader = new Xml.SpiceHeader();
//        spiceHeader.level = level;
//        for (String spiceLine: spiceLines)
//            spiceHeader.spiceLines.add(spiceLine);
//        t.spiceHeaders.add(spiceHeader);
//    }
//
//    private Object makeMenuEntry(Xml.Technology t, Object entry) throws IllegalAccessException, InvocationTargetException {
//        if (classArcProto.isInstance(entry))
//            return t.findArc((String)ArcProto_getName.invoke(entry));
//        if (classPrimitiveNode.isInstance(entry)) {
//            PrimitiveNode.Function fun = PrimitiveNodeFunctions.get(PrimitiveNode_getFunction.invoke(entry));
//            String name = (String)PrimitiveNode_getName.invoke(entry);
//            if (fun == PrimitiveNode.Function.PIN) {
//                Xml.MenuNodeInst n = new Xml.MenuNodeInst();
//                n.protoName = name;
//                n.function = PrimitiveNode.Function.PIN;
//                return n;
//            }
//            return t.findNode(name);
//        }
//        if (classNodeInst.isInstance(entry)) {
//            Xml.MenuNodeInst n = new Xml.MenuNodeInst();
//            n.protoName = (String)PrimitiveNode_getName.invoke(NodeInst_getProto.invoke(entry));
//            n.function = PrimitiveNodeFunctions.get(NodeInst_getFunction.invoke(entry));
//            n.rotation = (Integer)NodeInst_getAngle.invoke(entry);
//            for (Iterator<?> it = (Iterator)ElectricObject_getVariables.invoke(entry); it.hasNext(); ) {
//                Object var = it.next();
//                Object value = Variable_getObject.invoke(var);
//                if (!(value instanceof String)) continue;
//                n.text = (String)Variable_getObject.invoke(var);
//                Object td = Variable_getTextDescriptor.invoke(var);
//                n.fontSize = (Double)TextDescriptorSize_getSize.invoke(TextDescriptor_getSize.invoke(td));
//            }
//            return n;
//        }
//        if (entry.getClass().getName().equals("javax.swing.JPopupMenu$Separator"))
//            return "SEPARATOR";
//        assert entry instanceof String;
//        return entry;
//    }
//
//    private void makePortArcs(List<String> portArcs, Object tech, Object pp, Object excludeAp) throws IllegalAccessException, InvocationTargetException  {
//        Object[] connections = (Object[])PrimitivePort_getConnections.invoke(pp);
//        for (Object ap: connections) {
//            if (ap == null || ap == excludeAp) continue;
//            String arcName = (String)ArcProto_getName.invoke(ap);
//            if (Technology_findArcProto.invoke(tech, arcName) != ap) continue;
//            portArcs.add(arcName);
//        }
//    }
//
//    private boolean isPseudoLayer(Object layer) throws IllegalAccessException, InvocationTargetException {
//        int extraFun = (Integer)Layer_getFunctionExtras.invoke(layer);
//        final int PSEUDO =       010000;
//        return (extraFun & PSEUDO) != 0 || Layer_isPseudoLayer != null && (Boolean)Layer_isPseudoLayer.invoke(layer);
//    }
//
//    private static double round(double v) {
//        v = DBMath.round(v);
//        return v;
//    }
}
