/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: LibraryFiles.java
 *
 * Copyright (c) 2003, Static Free Software. 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.io.input;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.IdMapper;
import com.sun.electric.database.ImmutableNodeInst;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ERectangle;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.EDatabase;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.hierarchy.View;
import com.sun.electric.database.id.ArcProtoId;
import com.sun.electric.database.id.CellId;
import com.sun.electric.database.id.IdManager;
import com.sun.electric.database.id.LibId;
import com.sun.electric.database.id.PortProtoId;
import com.sun.electric.database.id.TechId;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.Setting;
import com.sun.electric.database.text.Version;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.ElectricObject;
import com.sun.electric.database.variable.MutableTextDescriptor;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.lib.LibFile;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.TechPool;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.Listener;
import com.sun.electric.tool.Tool;
import com.sun.electric.tool.io.ELIBConstants;
import com.sun.electric.tool.io.FileType;
import com.sun.electric.tool.ncc.basic.NccCellAnnotations;
import com.sun.electric.tool.user.CircuitChangeJobs;
import com.sun.electric.tool.user.ErrorLogger;
import com.sun.electric.tool.user.dialogs.OpenFile;
import com.sun.electric.util.ElapseTimer;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.Orientation;

import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * This class reads Library files (ELIB or readable dump) format.
 */
public abstract class LibraryFiles extends Input
{
	/** key of Variable holding true library of fake cell. */               public static final Variable.Key IO_TRUE_LIBRARY = Variable.newKey("IO_true_library");
	/** key of Variable to denote a dummy cell or library */                public static final Variable.Key IO_DUMMY_OBJECT = Variable.newKey("IO_dummy_object");

    /** Database where to load libraries */                                 final EDatabase database = EDatabase.serverDatabase();
    /** IdManager where to create IDs */                                    final IdManager idManager = database.getIdManager();
	/** The Library being input. */                                         protected Library lib;
	/** true if the library is the main one being read. */                  protected boolean topLevelLibrary;

	// the cell information
	/** The number of Cells in the file. */									protected int nodeProtoCount;
	/** A list of cells being read. */										protected Cell [] nodeProtoList;
	/** lambda value for each cell of the library */						protected double [] cellLambda;
	/** total number of cells in all read libraries */						protected static int totalCells;
	/** number of cells constructed so far. */								protected static int cellsConstructed;
	/** a List of scaled Cells that got created */							protected List<Cell> scaledCells;
	/** Number of errors in this LibraryFile */								protected int errorCount;
	/** the Electric version in the library file. */						protected Version version;
	/** true if old MOSIS CMOS technologies appear in the library */		protected boolean convertMosisCmosTechnologies;
	/** true to scale lambda by 20 */										protected boolean scaleLambdaBy20;
	/** true if rotation mirror bits are used */							protected boolean rotationMirrorBits;
    /** SizeCorrectors for used technologies */                             protected HashMap<Technology,Technology.SizeCorrector> sizeCorrectors = new HashMap<Technology,Technology.SizeCorrector>();
	/** font names obtained from FONT_ASSOCIATIONS */                       private String [] fontNames;
    /** buffer for reading text descriptors and variable flags. */          MutableTextDescriptor mtd = new MutableTextDescriptor();
    /** buffer for reading Variables. */                                    ArrayList<Variable> variablesBuf = new ArrayList<Variable>();

	/** the path to the library being read. */                              protected static String mainLibDirectory = null;
	/** collection of libraries and their input objects. */					private static List<LibraryFiles> libsBeingRead;
    /** collection of undefined Nodes/Arcs/Technologies */                  static Map<TechId, Set<Object>> undefinedTechsAndPrimitives;
    /** collection of elib cell bound */                                    static HashMap<CellId,ERectangle> elibCellBounds;
    /** Project preferences from library file. */                           HashMap<Setting,Object> projectSettings = new HashMap<Setting,Object>();
	protected static final boolean VERBOSE = false;
	protected static final double TINYDISTANCE = DBMath.getEpsilon()*2;

    static class NodeInstList
	{
		NodeInst []  theNode;
		NodeProto [] protoType;
		String []    name;
        TextDescriptor[] nameTextDescriptor;
		int []       lowX;
		int []       highX;
		int []       lowY;
		int []       highY;
		int []       anchorX;
		int []       anchorY;
		short []     rotation;
		int []       transpose;
        TextDescriptor[] protoTextDescriptor;
		int []       userBits;
        Variable[][] vars;

        NodeInstList(int nodeCount, boolean hasAnchor)
        {
            theNode = new NodeInst[nodeCount];
            protoType = new NodeProto[nodeCount];
            name = new String[nodeCount];
            nameTextDescriptor = new TextDescriptor[nodeCount];
            lowX = new int[nodeCount];
            highX = new int[nodeCount];
            lowY = new int[nodeCount];
            highY = new int[nodeCount];
            if (hasAnchor) {
                anchorX = new int[nodeCount];
                anchorY = new int[nodeCount];
            }
            rotation = new short[nodeCount];
            transpose = new int[nodeCount];
            protoTextDescriptor = new TextDescriptor[nodeCount];
            userBits = new int[nodeCount];
            vars = new Variable[nodeCount][];
        }
	};

	LibraryFiles(EditingPreferences ep) {
        super(ep);
    }

	/**
	 * Method to read a Library from disk.
	 * This method is for reading full Electric libraries in ELIB, JELIB, and Readable Dump format.
     * This method doesn't read project preferences contained in library file.
     * @param ep EditingPreferences
	 * @param fileURL the URL to the disk file.
	 * @param libName the name to give the library (null to derive it from the file path)
	 * @param type the type of library file (ELIB, JELIB, etc.)
	 * @param quick true to read the library without verbosity (used when reading a library internally).
	 * @return the read Library, or null if an error occurred.
	 */
	public static Library readLibrary(EditingPreferences ep, URL fileURL, String libName, FileType type, boolean quick) {
        return readLibrary(ep, fixMissingExtension(fileURL, type), libName, type, quick, null);
    }

	/**
	 * Method to see if adding a proper extension fixes a missing file.
	 * @param fileURL URL to the file, which may not exist.
	 * @param type the expected file type.
	 * @return URL to the file (with an extension, if adding one makes the file exist).
	 */
	public static URL fixMissingExtension(URL fileURL, FileType type)
	{
        // fix missing extension
        if (!TextUtils.URLExists(fileURL, null))
        {
            String fileName = fileURL.toString();
            String fileNameWithoutPath = TextUtils.getFileNameWithoutPath(fileName);
            if (fileNameWithoutPath.indexOf(".") == -1)
            {
                URL newURL = TextUtils.makeURLToFile(fileName + "." + type.getFirstExtension());
                if (TextUtils.URLExists(newURL, null)) fileURL = newURL;
            }
        }
        return fileURL;
	}

	/**
	 * Method to read a Library from disk.
	 * This method is for reading full Electric libraries in ELIB, JELIB, and Readable Dump format.
     * This method doesn't read project preferences contained in library file.
	 * @param fileURL the URL to the disk file.
	 * @param libName the name to give the library (null to derive it from the file path)
	 * @param type the type of library file (ELIB, JELIB, etc.)
	 * @param quick true to read the library without verbosity (used when reading a library internally).
	 * @param projectSettings an output map which is filled by project preferences of top library
	 * @return the read Library, or null if an error occurred.
	 */
	public static Library readLibrary(EditingPreferences ep, URL fileURL, String libName, FileType type, boolean quick,
        Map<Setting,Object> projectSettings)
	{
		if (fileURL == null) return null;
		long startTime = System.currentTimeMillis();
        errorLogger = ErrorLogger.newInst("Library Read");

        File f = new File(fileURL.getPath());

        if (f != null && f.exists()) {
            LibDirs.readLibDirs(f.getParent());
        }
		LibraryFiles.initializeLibraryInput();

		Library lib = null;
		boolean formerQuiet = isChangeQuiet();
		if (!formerQuiet) changesQuiet(true);
		try {
			// show progress
			if (!quick) startProgressDialog("library", fileURL.getFile());

			Cell.setAllowCircularLibraryDependences(true);

			boolean exists = TextUtils.URLExists(fileURL, null);
			if (!exists && (type != FileType.DELIB))
			{
				// Check if this is a DELIB
	            String fileName = fileURL.toString();
				fileURL = TextUtils.makeURLToFile(fileName + "." + FileType.DELIB.getFirstExtension());
				System.out.println("Attempting to open " + fileURL);
				StringBuilder errmsg = new StringBuilder();
				exists = TextUtils.URLExists(fileURL, errmsg);
				if (exists)
				{
					// It is a DELIB
					libName = null;        // Get the library name from fileURL
					type = FileType.DELIB; // Set the type to DELIB
				} else System.out.print(errmsg.toString());
			}
			if (exists)
			{
				// get the library name
				if (libName == null) libName = TextUtils.getFileNameWithoutExtension(fileURL);
				lib = readALibrary(ep, fileURL, null, libName, type, projectSettings);
			}
			if (LibraryFiles.VERBOSE)
				System.out.println("Done reading data for all libraries");

			LibraryFiles.cleanupLibraryInput();
			if (LibraryFiles.VERBOSE)
				System.out.println("Done instantiating data for all libraries");
		} finally {
			if (!quick) stopProgressDialog();
			Cell.setAllowCircularLibraryDependences(false);
		}
		if (!formerQuiet) changesQuiet(formerQuiet);
		if (lib != null && !quick)
		{
			long endTime = System.currentTimeMillis();
			ElapseTimer et = ElapseTimer.createInstanceByValues(startTime, endTime);
			System.out.println("Library " + fileURL.getFile() + " read, took " + et);
		}

        // if CVS is enabled, get status of library
//        if (CVS.isEnabled()) {
//            Update.updateOpenLibraries(Update.UpdateEnum.STATUS);
//        }
		if (quick) errorLogger.disablePopups();
		errorLogger.termLogging(true);

		return lib;
	}

	/**
	 * Method to read a Library from disk.
	 * This method is for reading full Electric libraries in ELIB, JELIB, and Readable Dump format.
	 * @param fileURL the URL to the disk file.
	 * @param type the type of library file (ELIB, JELIB, etc.)
	 * @return the read Library, or null if an error occurred.
	 */
	public static synchronized Map<String,Object> readProjectsSettingsFromLibrary(URL fileURL, FileType type, EditingPreferences ep)
	{
		if (fileURL == null) return null;
		long startTime = System.currentTimeMillis();
        errorLogger = ErrorLogger.newInst("Library Read Project Preferences");

		Library lib = null;
        StringBuilder errmsg = new StringBuilder();
        boolean exists = TextUtils.URLExists(fileURL, errmsg);
        if (!exists)
        {
            System.out.print(errmsg.toString());
            return null;
        }
        // handle different file types
        if (type == FileType.JELIB || type == FileType.DELIB) {
            TechPool techPool = EDatabase.serverDatabase().getTechPool();
            try {
                return settingsByXml(JELIB.readProjectSettings(fileURL, type, techPool, errorLogger));
            } finally {
                errorLogger.termLogging(true);
            }
        }
        LibraryFiles in;
        if (type == FileType.ELIB)
        {
            in = new ELIB(null);
            if (in.openBinaryInput(fileURL)) return null;
        } else if (type == FileType.READABLEDUMP)
        {
            in = new ReadableDump(null);
            if (in.openTextInput(fileURL)) return null;
        } else
        {
            System.out.println("Unknown import type: " + type);
            return null;
        }

        // determine whether this is top-level
        in.topLevelLibrary = true;

        // read the library
        boolean error = in.readProjectSettings(ep);
        in.closeInput();
        if (error)
        {
            System.out.println("Error reading " + lib);
            if (in.topLevelLibrary) mainLibDirectory = null;
            return null;
        }
        long endTime = System.currentTimeMillis();
		ElapseTimer et = ElapseTimer.createInstanceByValues(startTime, endTime);
        System.out.println("Library " + fileURL.getFile() + " read, took " + et);
        errorLogger.termLogging(true);

		return settingsByXml(in.projectSettings);
	}

    private static Map<String,Object> settingsByXml(Map<Setting,Object> settings) {
        Map<String,Object> settingsByXml = new HashMap<String,Object>();
        // settings is null if there were previous errors in the execution
        if (settings != null)
        {
            for (Map.Entry<Setting,Object> e: settings.entrySet())
                settingsByXml.put(e.getKey().getXmlPath(), e.getValue());
        }
        return settingsByXml;
    }

	/**
	 * Method to read a single library file.
     * @param ep EditingPreferences
	 * @param fileURL the URL to the file.
	 * @param lib the Library to read.
	 * If the Library is null, this is an entry-level library read, and one is created.
	 * If the Library is not null, this is a recursive read caused by a cross-library
	 * reference from inside another library.
	 * @param type the type of library file (ELIB, CIF, GDS, etc.)
	 * @return the read Library, or null if an error occurred.
	 */
	protected static Library readALibrary(EditingPreferences ep, URL fileURL, Library lib, String libName, FileType type,
                                          Map<Setting,Object> projectSettings)
	{
		// handle different file types
		LibraryFiles in;
		if (type == FileType.ELIB)
		{
			in = new ELIB(ep);
			if (in.openBinaryInput(fileURL)) return null;
		} else if (type == FileType.JELIB || type == FileType.DELIB)
		{
            try {
                LibId libId = lib != null ? lib.getId() : EDatabase.serverDatabase().getIdManager().newLibId(libName);
                in = new JELIB(ep, libId, fileURL, type);
            } catch (IOException e) {
            	if (Job.getDebug())
            		e.printStackTrace();
            	else
            		System.out.println("ERROR: " + e.getMessage());
                return null;
            }
		} else if (type == FileType.READABLEDUMP)
		{
			in = new ReadableDump(ep);
			if (in.openTextInput(fileURL)) return null;
		} else
		{
			System.out.println("ERROR: Unknown import type: " + type);
			return null;
		}

		// determine whether this is top-level
		in.topLevelLibrary = false;
		if (lib == null)
		{
			mainLibDirectory = TextUtils.getFilePath(fileURL);
            if (type == FileType.DELIB) {
                mainLibDirectory = mainLibDirectory.replaceAll(libName+"."+type.getFirstExtension(), "");
            }
			in.topLevelLibrary = true;
		}

		if (lib == null)
		{
			// create a new library
			lib = Library.newInst(libName, fileURL);
		}

		in.lib = lib;

		// read the library
		boolean error = in.readInputLibrary(ep);
		in.closeInput();
		if (error)
		{
			System.out.println("Error reading " + lib);
			if (in.topLevelLibrary) mainLibDirectory = null;
			return null;
		}
//        if (CVS.isEnabled()) {
//            CVSLibrary.addLibrary(lib);
//        }
        if (projectSettings != null)
            projectSettings.putAll(in.projectSettings);
		return in.lib;
	}

    /**
     * Reload a library from disk.  Note this is different from calling
     * reloadLibraryCells(List<Cell>) with a list of Cells in the library,
     * because it also reloads new cells from disk that are not currently in memory.
     * @param ep EditingPreferences
     * @param lib the Library to reload.
	 * @return mapping of Library/Cell/Export IDs, null if the library was renamed.
     */
    public static IdMapper reloadLibrary(EditingPreferences ep, Library lib) {
        if (!lib.isFromDisk()) {
            System.out.println("No disk file associated with this library, cannot reload from disk");
            return null;
        }
        FileType type = OpenFile.getOpenFileType(lib.getLibFile().getPath(), FileType.JELIB);

        // rename old library, read in disk library as replacement
        // replace all cells in old library by newly read-in cells from disk library

        String name = lib.getName();
        URL libFile = lib.getLibFile();
        boolean isCurrent = Library.getCurrent() == lib;
        IdMapper idMapper = lib.setName("___old___"+name);
        if (idMapper == null) return null;
        lib = idMapper.get(lib.getId()).inDatabase(EDatabase.serverDatabase());
        lib.setHidden();                // hide the old library
        startProgressDialog("library", name);
        Library newLib = readLibrary(ep, libFile, name, type, true);
        stopProgressDialog();
        if (isCurrent)
            Job.setCurrentLibraryInJob(newLib);

        // replace all old cells with new cells
        Cell.setAllowCircularLibraryDependences(true);
        for (Iterator<Cell> it = lib.getCells(); it.hasNext(); ) {
            Cell oldCell = it.next();
            String cellName = oldCell.getCellName().toString();

            Cell newCell = newLib.findNodeProto(cellName);
            if (newCell == null) {
                // doesn't exist in new library, copy it over
                System.out.println("Warning, Cell "+oldCell.describe(false)+" does not exist in reloaded library. Copying it to reloaded library.");
                newCell = Cell.copyNodeProto(oldCell, newLib, cellName, true);
            }
            // replace all usages of old cell with new cell
            List<NodeInst> instances = new ArrayList<NodeInst>();
            for(Iterator<NodeInst> it2 = oldCell.getInstancesOf(); it2.hasNext(); ) {
                instances.add(it2.next());
            }
            for (NodeInst ni : instances) {
                CircuitChangeJobs.replaceNodeInst(ni, newCell, PrimitiveNode.Function.UNKNOWN, true, true, false, ep);
            }
        }
        Cell.setAllowCircularLibraryDependences(false);

        // close temporary library
        lib.kill("delete old library");
        System.out.println("Reloaded library "+newLib.getName()+" from disk.");
        return idMapper;
    }

    /**
     * Reload Cells from disk.  Other cells in the library will not be affected.
     * These cells must all be from the same library.
     */
//    public static void reloadLibraryCells(List<Cell> cellList) {
//        if (cellList == null || cellList.size() == 0) return;
//        Library lib = cellList.get(0).getLibrary();
//
//        if (!lib.isFromDisk()) {
//            System.out.println("No disk file associated with this library, cannot reload from disk");
//            return;
//        }
//        FileType type = OpenFile.getOpenFileType(lib.getLibFile().getPath(), FileType.JELIB);
//
//        // Load disk library as temp library.
//        // Then, copy over new cells from temp library to replace current cells in memory,
//        // then close temp library.
//
//        String name = lib.getName();
//        URL libFile = lib.getLibFile();
//        startProgressDialog("library", name);
//        Library newLib = readLibrary(libFile, "___reloaded___"+name, type, true, iconParams);
//        stopProgressDialog();
//        newLib.setHidden();
//
//        // replace all old cells with new cells
//        Cell.setAllowCircularLibraryDependences(true);
//        for (Cell oldCell : cellList) {
//            String cellName = oldCell.getCellName().toString(); // contains version and view info
//
//            Cell newCell = newLib.findNodeProto(cellName);
//            if (newCell == null) {
//                System.out.println("Cell "+oldCell.describe(false)+" cannot be reloaded, it does not exist in disk library");
//                continue;
//            }
//
//            // rename oldCell
//            String renamedName = "___old___"+oldCell.getName();
//            IdMapper idMapper = oldCell.rename(renamedName, renamedName);      // must not contain version or view info
//            if (idMapper == null) continue;
//            oldCell = idMapper.get(oldCell.getId()).inDatabase(EDatabase.serverDatabase());
//
//            // copy newCell over with oldCell's name
//            newCell = Cell.copyNodeProto(newCell, lib, cellName, true);
//
//            // replace all usages of old cell with new cell
//            List<NodeInst> instances = new ArrayList<NodeInst>();
//            for(Iterator<NodeInst> it2 = oldCell.getInstancesOf(); it2.hasNext(); ) {
//                instances.add(it2.next());
//            }
//            for (NodeInst ni : instances) {
//                CircuitChangeJobs.replaceNodeInst(ni, newCell, true, true);
//            }
//
//            // kill oldCell
//            oldCell.kill();
//        }
//        Cell.setAllowCircularLibraryDependences(false);
//
//        // close temp library
//        newLib.kill("delete temp library");
//        System.out.println("Reloaded Cells from disk.");
//    }

	// *************************** THE CREATION INTERFACE ***************************

	public static void initializeLibraryInput()
	{
		libsBeingRead = new ArrayList<LibraryFiles>();
        undefinedTechsAndPrimitives = new HashMap<TechId, Set<Object>>();
        elibCellBounds = new HashMap<CellId,ERectangle>();
    }

	public boolean readInputLibrary(EditingPreferences ep)
	{
		// add this reader to the list
        assert(!libsBeingRead.contains(this));
        libsBeingRead.add(this);
		//libsBeingRead.put(lib, this);
		scaledCells = new ArrayList<Cell>();

		try
		{
			if (readTheLibrary(false, null, ep))
                return true;
            Map<Cell,Variable[]> originalVars = createLibraryCells(false, ep);
            realizeCellVariables(originalVars);
            return false;
		} catch (IOException e)
		{
			System.out.println("End of file reached while reading " + filePath);
			return true;
		}
	}

    abstract boolean readTheLibrary(boolean onlyProjectSettings, LibraryStatistics.FileContents fc, EditingPreferences ep) throws IOException;
    abstract Map<Cell,Variable[]> createLibraryCells(boolean onlyProjectSettings, EditingPreferences ep);

    private void realizeCellVariables(Map<Cell,Variable[]> originalVars) {
        HashSet<Cell.CellGroup> visitedCellGroups = new HashSet<Cell.CellGroup>();
        for (Cell cell: originalVars.keySet()) {
            Cell.CellGroup cellGroup = cell.getCellGroup();
            if (cellGroup != null) {
	            if (visitedCellGroups.contains(cellGroup)) continue;
	            realizeCellVariables(cellGroup, originalVars);
	            visitedCellGroups.add(cellGroup);
            }
        }
    }

    /**
     * Realize cell variables on all cells of a cell group
     * @param cellGroup the cell group to realize variables
     * @param originalVars original variables from disk library
     */
    private void realizeCellVariables(Cell.CellGroup cellGroup, Map<Cell,Variable[]> originalVars) {
        // Determine and create group parameters
        Cell cellOwner = cellGroup.getParameterOwner();
        HashMap<Variable.AttrKey,Variable> groupParams = null;
        if (cellOwner != null) {
            groupParams = new HashMap<Variable.AttrKey,Variable>();
            Variable[] ownerVars = originalVars.get(cellOwner);
            for (Variable var: ownerVars) {
                if (!var.getTextDescriptor().isParam()) continue;
                if (cellOwner.isDeprecatedVariable(var.getKey())) continue;
                if (var.getKey() == NccCellAnnotations.NCC_ANNOTATION_KEY) continue;
                var = var.withInherit(true);
                cellGroup.addParam(var);
                groupParams.put((Variable.AttrKey)var.getKey(), var);
            }
        }

        /**
         * Modify attributes of group parameters in each icon and schematic cell
         * Realize variables
         */
        for (Iterator<Cell> it = cellGroup.getCells(); it.hasNext(); ) {
            Cell cell = it.next();
            Variable[] origVars = originalVars.get(cell);
            if (cell.isIcon() || cell.isSchematic())
                realizeCellVariables(cell, origVars, groupParams);
            else
                realizeVariables(cell, origVars);
        }
    }

    private void realizeCellVariables(Cell cell, Variable[] origVars, HashMap<Variable.AttrKey,Variable> groupParams) {
        int foundParams = 0;
        for (Variable var: origVars) {
            if (var.isAttribute()) {
                Variable.Key varKey = var.getKey();
                Variable groupParam = groupParams.get(varKey);
                if (groupParam != null) {
                    if (!var.getTextDescriptor().isParam() && cell.getCellGroup() != null) {
                        String msg = "Attribute \"" + var.getTrueName() + "\" on " + cell +
                                " must be parameter as on " + cell.getCellGroup().getParameterOwner();
                        errorLogger.logError(msg, EPoint.fromLambda(var.getXOff(), var.getYOff()), cell, 2);
                        var = var.withParam(true);
                    }
                    var = var.withInherit(true);
                    if (!var.getObject().equals(groupParam.getObject()) && cell.getCellGroup() != null) {
                        String msg = "Parameter \"" + var.getTrueName() +
                                "\" has value " + var.getPureValue(-1) + " on " + cell +
                                " instead of " + groupParam.getPureValue(-1) + " as on " + cell.getCellGroup().getParameterOwner();
                        errorLogger.logError(msg, EPoint.fromLambda(var.getXOff(), var.getYOff()), cell, 2);
                        var = var.withObject(groupParam.getObject());
                    }
                    if (var.getUnit() != groupParam.getUnit() && cell.getCellGroup() != null) {
                        String msg = "Parameter \"" + var.getTrueName() +
                                "\" has unit " + var.getUnit() + " on " + cell +
                                " instead of " + groupParam.getUnit() + " as on " + cell.getCellGroup().getParameterOwner();
                        errorLogger.logError(msg, EPoint.fromLambda(var.getXOff(), var.getYOff()), cell, 2);
                        var = var.withUnit(groupParam.getUnit());
                    }
                    foundParams++;
                    cell.setTextDescriptor(var.getKey(), var.getTextDescriptor());
                    continue;
                }
                if (var.getTextDescriptor().isParam() && cell.getCellGroup() != null) {
                    String msg = cell + " has extra parameter \"" + var.getTrueName() +
                            "\" compared to " + cell.getCellGroup().getParameterOwner();
                    errorLogger.logError(msg, EPoint.fromLambda(var.getXOff(), var.getYOff()), cell, 2);
                    var = var.withParam(false);
                    if (var.getKey() == NccCellAnnotations.NCC_ANNOTATION_KEY)
                        var = var.withInherit(false).withInterior(false);
                }
            }
            realizeVariable(cell, var);
        }
        if (foundParams == groupParams.size()) return;

        // Create parameters which were omitted in the disk library
        HashSet<Variable.AttrKey> omittedParams = new HashSet<Variable.AttrKey>(groupParams.keySet());
        for (Variable var: origVars)
            omittedParams.remove(var.getKey());
        assert omittedParams.size() + foundParams == groupParams.size();

        // For icon cell try to find variables on an example icon in schematic cell
        Variable[] exampleVars = null;
        if (cell.isIcon()) {
            for (Cell schCell : cell.getCellsInGroup()) {
                if (!schCell.isSchematic()) continue;
                exampleVars = findVarsOnExampleIcon(schCell, cell);
                if (exampleVars == null) continue;
                break;
            }
        }
        for (Variable.AttrKey omittedParam: omittedParams) {
            Variable param = groupParams.get(omittedParam);
            String msg = cell + " must have parameter \"" + param.getTrueName() + "\"";
            if (cell.getCellGroup() != null) msg += " as on " + cell.getCellGroup().getParameterOwner();
            Variable exampleParam = null;
            if (exampleVars != null) {
                for (Variable var: exampleVars) {
                    if (var == null || var.getKey() != omittedParam) continue;
                    exampleParam = var;
                    break;
                }
            }
            if (exampleParam != null) {
                TextDescriptor td = exampleParam.getTextDescriptor();
                td = td.withInherit(true).withParam(true).withUnit(param.getUnit());
                boolean interior = !exampleParam.isDisplay();
                td = td.withInterior(interior).withDisplay(true);
                param = param.withTextDescriptor(td);
            }
            assert param.getTextDescriptor().isParam() && param.isInherit();
            errorLogger.logError(msg, EPoint.fromLambda(param.getXOff(), param.getYOff()), cell, 2);
            cell.setTextDescriptor(param.getKey(), param.getTextDescriptor());
        }
    }

    abstract Variable[] findVarsOnExampleIcon(Cell parentCell, Cell iconCell);

	protected void scanNodesForRecursion(Cell cell, HashSet<Cell> markCellForNodes, HashSet<Cell> patchedCells, NodeProto [] nil, int start, int end)
	{
		// scan the nodes in this cell and recurse
		for(int j=start; j<end; j++)
		{
			NodeProto np = nil[j];
			if (np instanceof PrimitiveNode) continue;
			Cell otherCell = (Cell)np;
			if (otherCell == null) continue;

			// subcell: make sure that cell is setup
			if (markCellForNodes.contains(otherCell)) continue;

			LibraryFiles reader = this;
			if (otherCell.getLibrary() != cell.getLibrary())
				reader = getReaderForLib(otherCell.getLibrary());

			// subcell: make sure that cell is setup
			if (reader != null)
				reader.realizeCellsRecursively(otherCell, markCellForNodes, patchedCells, null, 0);
		}
		markCellForNodes.add(cell);
	}

	/**
	 * Method to read project preferences from a Library.
	 * This method is never called.
	 * Instead, it is always overridden by the appropriate read subclass.
	 * @return true on error.
	 */
	protected boolean readProjectSettings(EditingPreferences ep) { return true; }

	/**
	 * Method to find the View to use for an old View name.
	 * @param viewName the old View name.
	 * @return the View to use (null if not found).
	 */
	protected View findOldViewName(String viewName)
	{
		if (version.getMajor() < 8)
		{
			if (viewName.equals("compensated")) return View.LAYOUTCOMP;
			if (viewName.equals("skeleton")) return View.LAYOUTSKEL;
			if (viewName.equals("simulation-snapshot")) return View.DOCWAVE;
			if (viewName.equals("netlist-netlisp-format")) return View.NETLISTNETLISP;
			if (viewName.equals("netlist-rsim-format")) return View.NETLISTRSIM;
			if (viewName.equals("netlist-silos-format")) return View.NETLISTSILOS;
			if (viewName.equals("netlist-quisc-format")) return View.NETLISTQUISC;
			if (viewName.equals("netlist-als-format")) return View.NETLISTALS;
		}
		return null;
	}

	protected Technology findTechnologyName(String name)
	{
		Technology tech = null;
		if (convertMosisCmosTechnologies)
		{
			if (name.equals("mocmossub")) tech = Technology.getMocmosTechnology(); else
				if (name.equals("mocmos")) tech = Technology.findTechnology("mocmosold");
		}
		if (tech == null) tech = Technology.findTechnology(name);
		if (tech == null && name.equals("logic"))
			tech = Schematics.tech();
		if (tech == null && (name.equals("epic8c") || name.equals("epic7c")))
			tech = Technology.findTechnology("epic7s");
		return tech;
	}

	/**
	 * Method to read an external library file, given its name as stored on disk.
	 * Attempts to find the file in many different ways, including asking the user.
	 * @param theFileName the full path to the file, as written to disk.
	 * @return a Library that was read. If library cannot be read or found, creates
	 * a Library called DUMMYname, and returns that.
	 */
	protected Library readExternalLibraryFromFilename(String theFileName, FileType defaultType, EditingPreferences ep)
	{
		String fileExt = TextUtils.getExtension(theFileName);
		boolean found = (defaultType != null && FileType.matchExtension(fileExt));

		// get the path to the library file
		// check if the potential extension is valid as FileType
		// If name has a valid FileType extension, then removes it.
		String legalLibName = (found) ? TextUtils.getFileNameWithoutExtension(theFileName) : theFileName;
		
		// Checking if the library is already open
		Library elib = Library.findLibrary(legalLibName);
		if (elib != null) return elib;

        URL externalURL = searchExternalLibraryFromFilename(mainLibDirectory, theFileName, defaultType);

        boolean exists = (externalURL != null);
        // last option: let user pick library location
		if (!exists)
		{
			String pt = null;
			while (true) {
				// continue to ask the user where the library is until they hit "cancel"
				String description = "Reference library '" + theFileName + "'";
				pt = OpenFile.chooseInputFile(FileType.LIBFILE, description, ep);
				if (pt == null) {
					// user canceled, break
					break;
				}
				// see if user chose a file we can read
				externalURL = TextUtils.makeURLToFile(pt);
				if (externalURL != null) {
					exists = TextUtils.URLExists(externalURL, null);
					if (exists) {
						// good file, opened it, get out of here
						break;
					}
				}
			}
		}

		if (exists)
		{
			System.out.println("Reading referenced library " + externalURL.getFile());
            elib = Library.newInst(legalLibName, externalURL);
		}

        if (elib != null)
        {
            // read the external library
            String oldNote = getProgressNote();
            setProgressValue(0);
            setProgressNote("Reading referenced library " + legalLibName + "...");

			// get the library name
            FileType importType = OpenFile.getOpenFileType(externalURL.getFile(), defaultType);
			String eLibName = TextUtils.getFileNameWithoutExtension(TextUtils.URLtoString(externalURL), true);
            elib = readALibrary(ep, externalURL, elib, eLibName, importType, null);
            setProgressValue(100);
            setProgressNote(oldNote);
        }

        if (elib == null)
        {
            System.out.println("Error: cannot find referenced library " + theFileName);
            System.out.println("...Creating new "+legalLibName+" Library instead");
            elib = Library.newInst(legalLibName, null);
            elib.setLibFile(TextUtils.makeURLToFile(theFileName));
            elib.clearFromDisk();
        }
//		if (failed) elib->userbits |= UNWANTEDLIB; else
//		{
//			// queue this library for announcement through change control
//			io_queuereadlibraryannouncement(elib);
//		}

		return elib;
	}

	/**
	 * Method to search an external library file, given its name as stored on disk.
	 * Attempts to find the file in many different ways, including asking the user.
	 * @param theFileName the full path to the file, as written to disk.
     * @param defaultType default file type
	 * @return an URL to Library file.
	 */
    public static URL searchExternalLibraryFromFilename(String mainLibDirectory, String theFileName, FileType defaultType) {
		// see if this library is already read in
		String libFileName = theFileName;

		// special case if the library path came from a different computer system and still has separators
		while (libFileName.endsWith("\\") || libFileName.endsWith(":") || libFileName.endsWith("/"))
			libFileName = libFileName.substring(0, libFileName.length()-1);
		int backSlashPos = libFileName.lastIndexOf('\\');
		int colonPos = libFileName.lastIndexOf(':');
		int slashPos = libFileName.lastIndexOf('/');
		int charPos = Math.max(backSlashPos, Math.max(colonPos, slashPos));
		if (charPos >= 0)
		{
			libFileName = libFileName.substring(charPos+1);
		}
		String libName = libFileName;
		FileType importType = OpenFile.getOpenFileType(libName, defaultType);
        FileType preferredType = importType;

        // this is just to remove the extension from the library name string
		if (libName.endsWith(".elib"))
		{
			libName = libName.substring(0, libName.length()-5);
		} else if (libName.endsWith(".jelib"))
		{
			libName = libName.substring(0, libName.length()-6);
        } else if (libName.endsWith(".delib"))
        {
            libName = libName.substring(0, libName.length()-6);
		} else if (libName.endsWith(".txt"))
		{
			libName = libName.substring(0, libName.length()-4);
		} else
		{
			// no recognizable extension, add one to the file name
			libFileName += "." + defaultType.getFirstExtension();
		}

        StringBuilder errmsg = new StringBuilder();

        // first try the library name with the extension it came with
        // However, do not look in electric library area to avoid problems with spice configurations for old chips
        URL externalURL = getLibrary(mainLibDirectory, libName + "." + preferredType.getFirstExtension(), theFileName, errmsg, true);
        // Now try all file types, starting with JELIB
        // try JELIB
        if (externalURL == null && preferredType != FileType.JELIB) {
            externalURL = getLibrary(mainLibDirectory, libName + "." + FileType.JELIB.getFirstExtension(), theFileName, errmsg, true);
        }
        // try ELIB
        if (externalURL == null && preferredType != FileType.ELIB) {
            externalURL = getLibrary(mainLibDirectory, libName + "." + FileType.ELIB.getFirstExtension(), theFileName, errmsg, true);
        }
        // try DELIB
        if (externalURL == null && preferredType != FileType.DELIB) {
            externalURL = getLibrary(mainLibDirectory, libName + "." + FileType.DELIB.getFirstExtension(), theFileName, errmsg, true);
        }
        // try readable dump
        if (externalURL == null && preferredType != FileType.READABLEDUMP) {
            externalURL = getLibrary(mainLibDirectory, libName + "." + FileType.READABLEDUMP.getFirstExtension(), theFileName, errmsg, true);
        }

		if (externalURL == null) {
			System.out.println("Error: cannot find referenced library " + libName+":");
			System.out.print(errmsg.toString());
        }
        return externalURL;
    }

    /** Get the URL to the library named libFileName
     * @param libFileName the library file name (with extension)
     * @param originalPath the original, exact path of the reference if any
     * @param errmsg a StringBuilder into which errors may be placed.
     * @param checkElectricLib to force search in Electric library area
     * @return null if not found, or valid URL if file found
     */
    private static URL getLibrary(String mainLibDirectory, String libFileName, String originalPath, StringBuilder errmsg, boolean checkElectricLib)
    {
        // library does not exist: see if file is in the same directory as the main file
		URL firstURL = TextUtils.makeURLToFile(mainLibDirectory + libFileName);
        boolean exists = TextUtils.URLExists(firstURL, errmsg);
        if (exists) return firstURL;
        HashMap<String,String> searchedURLs = new HashMap<String,String>();

        // try again library file locations
        for (Iterator<String> libIt = LibDirs.getLibDirs(); libIt.hasNext(); )
        {
        	String next = libIt.next();
        	
        	// Trying the main library directory first
			URL url = TextUtils.makeURLToFile(mainLibDirectory + File.separator + next + File.separator + libFileName);
            exists = TextUtils.URLExists(url, errmsg);
            if (exists) return url;
            if (url != null) searchedURLs.put(url.getFile(), url.getFile());
        	
        	// Trying the local location where Electric started
			url = TextUtils.makeURLToFile(next + File.separator + libFileName);
            exists = TextUtils.URLExists(url, errmsg);
            if (exists) return url;
            if (url != null) searchedURLs.put(url.getFile(), url.getFile());
        }

        // check the current working directory
        // (Note that this is not necessarily the same as the mainLibDirectory above)
        // Do NOT search User.getCurrentWorkingDir, as another Electric process can
        // modify that during library read instead, search System.getProperty("user.dir");
        URL thirdURL = TextUtils.makeURLToFile(System.getProperty("user.dir") + File.separator + libFileName);
        if (thirdURL != null && !searchedURLs.containsKey(thirdURL.getFile()))
        {
            exists = TextUtils.URLExists(thirdURL, errmsg);
            if (exists) return thirdURL;
            if (thirdURL != null) searchedURLs.put(thirdURL.getFile(), thirdURL.getFile());
        }

        // try the exact path specified in the reference
        if (originalPath != null) {
    		URL url = TextUtils.makeURLToFile(originalPath);
        	String fileName = url.getFile();
            File libFile = new File(fileName);
            originalPath = libFile.getParent();

            URL secondURL = TextUtils.makeURLToFile(originalPath + File.separator + libFileName);
            if (secondURL != null && !searchedURLs.containsKey(secondURL.getFile()))
            {
                exists = TextUtils.URLExists(secondURL, errmsg);
                if (exists) return secondURL;
                if (secondURL != null) searchedURLs.put(secondURL.getFile(), secondURL.getFile());
            }
        }

		if (checkElectricLib)
        {
            // try the Electric library area
            URL url = LibFile.getLibFile(libFileName);
            exists = TextUtils.URLExists(url, errmsg);
            if (exists) return url;
        }
        return null;
    }

	public static void cleanupLibraryInput()
	{
        setProgressValue(0);
        setProgressNote("Constructing cell contents...");

		// Compute technology of new cells
		Set<Cell> uncomputedCells = new HashSet<Cell>();
		for(LibraryFiles reader : libsBeingRead)
		{
			for(int cellIndex=0; cellIndex<reader.nodeProtoCount; cellIndex++)
			{
				Cell cell = reader.nodeProtoList[cellIndex];
				if (cell == null) continue;
				if (cell.getLibrary() != reader.lib) continue;
				uncomputedCells.add(cell);
			}
		}
		for(LibraryFiles reader : libsBeingRead)
		{
			for(int cellIndex=0; cellIndex<reader.nodeProtoCount; cellIndex++)
			{
				Cell cell = reader.nodeProtoList[cellIndex];
				if (cell == null) continue;
				if (cell.getLibrary() != reader.lib) continue;
				reader.computeTech(cell, uncomputedCells);
			}
		}

		// clear flag bits for scanning the library hierarchically
		totalCells = 0;
		HashSet<Cell> markCellForNodes = new HashSet<Cell>();
		for(LibraryFiles reader : libsBeingRead)
		{
			totalCells += reader.nodeProtoCount;
			for(int cellIndex=0; cellIndex<reader.nodeProtoCount; cellIndex++)
			{
				Cell cell = reader.nodeProtoList[cellIndex];
				if (cell == null) continue;
				if (cell.getLibrary() != reader.lib) continue;
				reader.cellLambda[cellIndex] = reader.computeLambda(cell, cellIndex);
				cell.setTempInt(cellIndex);
			}
		}
		cellsConstructed = 0;

		// now recursively adjust lambda sizes
		if (LibraryFiles.VERBOSE)
			System.out.println("Preparing to compute scale factors");
		for(int i=0; i<20; i++)
		{
			boolean unchanged = true;
			for(LibraryFiles reader : libsBeingRead)
			{
				for(int cellIndex=0; cellIndex<reader.nodeProtoCount; cellIndex++)
				{
					Cell cell = reader.nodeProtoList[cellIndex];
					if (cell == null) continue;
					if (cell.getLibrary() != reader.lib) continue;
					if (reader.spreadLambda(cell, cellIndex))
					{
						unchanged = false;
					}
				}
			}
			if (unchanged) break;
		}
		if (LibraryFiles.VERBOSE)
			System.out.println("Finished computing scale factors");

		// recursively create the cell contents
        HashSet<Cell> patchedCells = new HashSet<Cell>();
		for(LibraryFiles reader : libsBeingRead)
		{
			for(int cellIndex=0; cellIndex<reader.nodeProtoCount; cellIndex++)
			{
				Cell cell = reader.nodeProtoList[cellIndex];
				if (cell == null) continue;
				if (markCellForNodes.contains(cell)) continue;
				reader.realizeCellsRecursively(cell, markCellForNodes, patchedCells, null, 0);
			}
		}
        for (LibraryFiles reader: libsBeingRead)
            reader.lib.clearChanged(patchedCells);

		// tell which libraries had extra "scaled" cells added
		boolean first = true;
		for(LibraryFiles reader : libsBeingRead)
		{
			if (reader.scaledCells != null && reader.scaledCells.size() != 0)
			{
				if (first)
				{
					System.out.println("WARNING: to accommodate scaling inconsistencies, these cells were created:");
					first = false;
				}
				StringBuffer sb = new StringBuffer();
				sb.append("   Library " + reader.lib.getName() + ":");
				for(Cell cell : reader.scaledCells)
				{
					sb.append(" " + cell.noLibDescribe());
				}
				System.out.println(sb.toString());
			}
		}

		// adjust for old library conversion
//		convertOldLibraries();

		// broadcast the library-read to all listeners
		for(Iterator<Listener> it = Tool.getListeners(); it.hasNext(); )
		{
			Listener listener = it.next();
			for(LibraryFiles reader : libsBeingRead)
			{
				listener.readLibrary(reader.lib);
			}
		}
        if (!undefinedTechsAndPrimitives.isEmpty()) {
            String prims = "";
            for (TechId techId: undefinedTechsAndPrimitives.keySet())
            {
                int count = 0;
                // Adding missing node/arc definitions
                for (Object primId : undefinedTechsAndPrimitives.get(techId))
                {
                	String extra = "";
                	if (primId instanceof ArcProtoId)
                		extra = "(arc)";
                    prims += " " + primId + extra + ", ";
                    count += primId.toString().length();//primId.name.length();
                    if (count > 50) // 50 is an arbitrary number
                    {
                        count = 0;
                        prims += "\n";
                    }
                }
                Job.getUserInterface().showErrorMessage("Library contains unknown nodes/arcs from the technology '" + techId + "':\n" + prims,
                    "Unknown nodes/arcs/technologies");
            }
        }

        // clean up initialization (free LibraryFiles for garbage collection)
        libsBeingRead.clear();
        undefinedTechsAndPrimitives.clear();
        elibCellBounds.clear();
	}

// 	private static void convertOldLibraries()
// 	{
// 		// see if the MOSIS CMOS technology now has old-style state information
// 		MoCMOS.tech.convertOldState();
// 	}

	protected LibraryFiles getReaderForLib(Library lib) {
        for (LibraryFiles reader : libsBeingRead) {
            if (reader.lib == lib) return reader;
        }
        return null;
    }

    Technology.SizeCorrector getSizeCorrector(Technology tech) {
        Technology.SizeCorrector corrector = sizeCorrectors.get(tech);
        if (corrector == null) {
            corrector = tech.getSizeCorrector(version, projectSettings, false, false);
            sizeCorrectors.put(tech, corrector);
        }
        return corrector;
    }

	String convertCellName(String s)
	{
		StringBuffer buf = null;
		for (int i = 0; i < s.length(); i++)
		{
			char ch = s.charAt(i);
			if (TextUtils.isBadCellNameCharacter(ch))
			{
				if (buf == null)
				{
					buf = new StringBuffer();
					buf.append(s.substring(0, i));
				}
				buf.append('-');
				continue;
			} else if (buf != null)
			{
				buf.append(ch);
			}
		}
		if (buf != null)
		{
			String newS = buf.toString();
			System.out.println("Cell name " + s + " was converted to " + newS);
			return newS;
		}
		return s;
	}

	/**
	 * Method to convert name of Geometric object.
	 * @param value name of object
	 * @param isDisplay true if this is displayable variable.
	 */
	protected static String convertGeomName(Object value, boolean isDisplay)
	{
		if (value == null || !(value instanceof String)) return null;
		String str = (String)value;
		int indexOfAt = str.indexOf('@');
		if (isDisplay)
		{
			if (indexOfAt >= 0)
			{
				String newS = "";
				for (int i = 0; i < str.length(); i++)
				{
					char c = str.charAt(i);
					if (c == '@') c = '_';
					newS += c;
				}
				str = newS;
			}
		} else if (indexOfAt < 0) return null;
		return str;
	}

	/**
	 * Method to build a NodeInst.
     * @param nil arrays with data of new NodeInst
     * @param nodeIndex index in nil array
     * @param xoff x-offset of NodeInst in integer coordinates
     * @param yoff y-offset of NodeInst in integer coordinates
     * @param lambda scale factor
     * @param parent parent Cell
     * @param proto actual node prototype (may be different for instance of scaled Cells)
	 */
    void realizeNode(NodeInstList nil, int nodeIndex, int xoff, int yoff, double lambda, Cell parent, NodeProto proto)
    {
		double lowX = nil.lowX[nodeIndex]-xoff;
		double lowY = nil.lowY[nodeIndex]-yoff;
		double highX = nil.highX[nodeIndex]-xoff;
		double highY = nil.highY[nodeIndex]-yoff;
		Point2D center = new Point2D.Double(((lowX + highX) / 2) / lambda, ((lowY + highY) / 2) / lambda);
        EPoint size = EPoint.ORIGIN;
        if (proto instanceof PrimitiveNode) {
            PrimitiveNode pn = (PrimitiveNode)proto;
            EPoint diskSize = EPoint.fromLambda((highX - lowX) / lambda, (highY - lowY) / lambda);
            size = getSizeCorrector(pn.getTechnology()).getSizeFromDisk(pn, diskSize);
        }

		int rotation = nil.rotation[nodeIndex];
        boolean flipX = false;
        boolean flipY = false;
		if (rotationMirrorBits)
		{
			// new version: allow mirror bits
			if ((nil.transpose[nodeIndex]&1) != 0)
			{
                flipY = true;
				rotation = (rotation + 900) % 3600;
			}
			if ((nil.transpose[nodeIndex]&2) != 0)
			{
				// mirror in X
                flipX = true;
			}
			if ((nil.transpose[nodeIndex]&4) != 0)
			{
				// mirror in Y
                flipY = !flipY;
			}
		} else
		{
			// old version: just use transpose information
			if (nil.transpose[nodeIndex] != 0)
			{
                flipY = true;
				rotation = (rotation + 900) % 3600;
			}
		}
        Orientation orient = Orientation.fromJava(rotation, flipX, flipY);

		// figure out the grab center if this is a cell instance
		if (proto instanceof Cell)
		{
            if (nil.anchorX != null)
//            if (magic <= ELIBConstants.MAGIC13)
            {
                // version 13 and later stores and uses anchor location
                double anchorX = (nil.anchorX[nodeIndex]-xoff) / lambda;
                double anchorY = (nil.anchorY[nodeIndex]-yoff) / lambda;
                center.setLocation(anchorX, anchorY);
            } else
            {
                Cell subCell = (Cell)proto;
                ERectangle bounds = subCell.tree().getElibBounds(elibCellBounds);
                Point2D shift = new Point2D.Double(-bounds.getCenterX(), -bounds.getCenterY());
                FixpTransform trans = orient.pureRotate();
                trans.transform(shift, shift);
                center.setLocation(center.getX() + shift.getX(), center.getY() + shift.getY());
            }
		}

        int flags = ImmutableNodeInst.flagsFromElib(nil.userBits[nodeIndex]);
        int techBits = ImmutableNodeInst.techSpecificFromElib(nil.userBits[nodeIndex]);
        Integer cutAlignment = null;
        if (proto instanceof PrimitiveNode && ((PrimitiveNode)proto).isMulticut() && techBits != 0) {
            cutAlignment = Integer.valueOf(techBits);
            techBits = 0;
        }
        TextDescriptor nameTd = nil.nameTextDescriptor[nodeIndex];
        if (nameTd == null) {
            nameTd = ep.getNodeTextDescriptor();
        }
        TextDescriptor protoTd = nil.protoTextDescriptor[nodeIndex];
        if (protoTd == null) {
            protoTd = ep.getInstanceTextDescriptor();
        }
		NodeInst ni = NodeInst.newInst(parent, proto, nil.name[nodeIndex], nameTd,
                center, size, orient, flags, techBits, protoTd, Input.errorLogger);
        nil.theNode[nodeIndex] = ni;
//        if (proto instanceof PrimitiveNode) {
//            PrimitiveNode pn = (PrimitiveNode)proto;
//            PrimitiveNode.NodeSizeRule nodeSizeRule = pn.getMinSizeRule();
//            if (nodeSizeRule != null) {
//                if (size.getLambdaX() < nodeSizeRule.getWidth())
//                    Input.errorLogger.logWarning(" (" + parent + ") node " + ni.getName() + " width is less than minimum by " +
//                            (nodeSizeRule.getWidth() - size.getLambdaX()), ni, parent, null, 2);
//                if (size.getLambdaY() < nodeSizeRule.getHeight())
//                    Input.errorLogger.logWarning(" (" + parent + ") node " + ni.getName() + " height is less than minimum by " +
//                            (nodeSizeRule.getHeight() - size.getLambdaY()), ni, parent, null, 2);
//            }
//        }
        if (ni == null) return;
        Variable[] vars = nil.vars[nodeIndex];
        if (vars != null) {
            for (int j = 0; j < vars.length; j++) {
                Variable var = vars[j];
                if (var == null) continue;
	            // convert outline information
				if (var.getKey() == NodeInst.TRACE) {
                    if (proto instanceof PrimitiveNode && ((PrimitiveNode)proto).isHoldsOutline()) {
                        Object value = var.getObject();
                        if (value instanceof Integer[] || value instanceof Float[]) {
                            // convert outline information, if present
                            Number[] outline = (Number[])value;
                            int newLength = outline.length / 2;
                            EPoint [] newOutline = new EPoint[newLength];
                            double lam = outline instanceof Integer[] ? lambda : 1.0;
                            for(int k=0; k<newLength; k++) {
                                double oldX = outline[k*2].doubleValue()/lam;
                                double oldY = outline[k*2+1].doubleValue()/lam;
                                if (!Double.isNaN(oldX) && !Double.isNaN(oldY))
                                    newOutline[k] = EPoint.fromLambda(oldX, oldY);
                            }
                            var = var.withObject(newOutline);
                        }
                        realizeVariable(ni, var);
                    }
                    continue;
                }
                if (ni.isDeprecatedVariable(var.getKey())) continue;
                if (ni.isParam(var.getKey()))
                    ni.addParameter(var);
                else
                    ni.addVar(var.withParam(false));
            }
        }
        if (cutAlignment != null)
            ni.newVar(Technology.NodeLayer.CUT_ALIGNMENT, cutAlignment, ep);

        // if this was a dummy cell, log instance as an error so the user can find easily
        if (proto instanceof Cell && ((Cell)proto).getVar(IO_DUMMY_OBJECT) != null) {
            Input.errorLogger.logError("Instance of dummy cell "+proto.getName(), ni, parent, null, 1);
        }
    }

    void realizeVariables(ElectricObject eObj, Variable[] vars) {
        if (vars == null) return;
        for (Variable var: vars)
            realizeVariable(eObj, var);
    }

    private void realizeVariable(ElectricObject eObj, Variable var) {
    	if (var == null) return;

    	String parseExceptionDesc = var.describeParseException(eObj);
    	if (parseExceptionDesc != null)
    	{
    		// ignore parse warnings for code
    		if (!var.isCode())
    			logWarning("WARNING: " + parseExceptionDesc);
    	}

        if (eObj.isDeprecatedVariable(var.getKey())) {
            return;
        }

        if (eObj.isParam(var.getKey()) && eObj instanceof NodeInst) {
            ((NodeInst)eObj).addParameter(var);
            return;
        }

        var = var.withParam(false);
        if (var.getKey() == NodeInst.TRACE) {
            Object varObj = var.getObject();
            if (eObj instanceof NodeInst && varObj instanceof EPoint[]) {
                NodeInst ni = (NodeInst)eObj;
                if (!ni.isCellInstance() && !ImmutableNodeInst.isCellCenter(ni.getProto().getId())) {
                    ni.setTraceRelative((EPoint[])varObj, ni.getAnchorCenter(), ni.getOrient());
                    assert ni.getOrient() == Orientation.IDENT;
                }
            }
            return;
        }
        String origVarName = var.getKey().toString();
        // convert old port variables
        if (eObj instanceof NodeInst && var.getKey().getName().startsWith("ATTRP_")) {
            NodeInst ni = (NodeInst)eObj;
            // the form is "ATTRP_portName_variableName" with "\" escapes
            StringBuffer portName = new StringBuffer();
            String varName = null;
            int len = origVarName.length();
            for(int j=6; j<len; j++) {
                char ch = origVarName.charAt(j);
                if (ch == '\\') {
                    j++;
                    portName.append(origVarName.charAt(j));
                    continue;
                }
                if (ch == '_') {
                    varName = origVarName.substring(j+1);
                    break;
                }
                portName.append(ch);
            }
            if (varName != null) {
                String thePortName = portName.toString();
                PortProto pp = findPortProto(ni.getProto(), thePortName);
                PortInst pi = pp != null ? ni.findPortInstFromProto(pp) : null;
                if (pi != null) {
                    pi.newVar(Variable.newKey(varName), var.getObject(), var.getTextDescriptor());
                    return;
                }
            }
        }
        eObj.addVar(var);
    }

    static PortProto findPortProto(NodeProto np, String portId) {
        PortProtoId portProtoId = np.getId().newPortId(portId);
        PortProto pp = np.getPort(portProtoId);
        if (pp != null) return pp;
        if (np.getNumPorts() == 1 && portId.length() == 0)
            return np.getPort(0);
        if (np instanceof PrimitiveNode) {
            PrimitiveNode primNode = (PrimitiveNode)np;
            pp = primNode.findPortProto(portId);
            if (pp == null)
                pp = primNode.getTechnology().convertOldPortName(portId, primNode);
        }
        return pp;
    }

	/**
	 * Method to add meaning preferences to an ElectricObject from a List of strings.
	 * @param obj the Object to augment with meaning preferences.
	 * @param vars Variables with meaning preferences.
	 */
    void realizeMeaningPrefs(Object obj, Variable[] vars) {
        realizeMeaningPrefs(projectSettings, obj, vars);
	}

	/**
	 * Method to add meaning preferences to an ElectricObject from a List of strings.
     * @param projectSettings a map for result
	 * @param obj the Object to augment with meaning preferences.
	 * @param vars Variables with meaning preferences.
	 */
    static void realizeMeaningPrefs(HashMap<Setting,Object> projectSettings, Object obj, Variable[] vars) {
        HashMap<String,Object> settingsByPrefName = new HashMap<String,Object>();
        for (int i = 0; i < vars.length; i++) {
            Variable var = vars[i];
            if (var == null) continue;
            Object value = var.getObject();
            if (!(value instanceof String)) {
                if (value instanceof Short || value instanceof Byte)
                    value = Integer.valueOf(((Number)value).intValue());
                if (!(value instanceof Number) && !(value instanceof Boolean))
                    continue;
            }
            String prefName = var.getKey().getName();
            String prefPath = null;
            if (obj instanceof Technology) {
                prefPath = Technology.TECH_NODE + "/";
                Map<Setting,Object> convertedVars = ((Technology)obj).convertOldVariable(prefName, value);
                if (convertedVars != null) {
                    for (Map.Entry<Setting,Object> e: convertedVars.entrySet()) {
                        Setting setting = e.getKey();
                        prefName = setting.getPrefName();
                        value = e.getValue();
                        projectSettings.put(setting, value);
                    }
                    continue;
                }
            } else if (obj instanceof Tool) {
                prefPath = ((Tool)obj).prefs.relativePath() + "/";
            }
            settingsByPrefName.put(prefPath + prefName, value);
        }
        Setting.Group grp = obj instanceof Technology ? ((Technology)obj).getProjectSettings() : ((Tool)obj).getProjectSettings();
        for (Setting setting: grp.getSettings()) {
            Object value = settingsByPrefName.get(setting.getPrefPath());
            if (value != null)
                projectSettings.put(setting, value);
        }
	}

    TextDescriptor makeDescriptor(int td0, int td1) {
        mtd.setCBits(td0, fixTextDescriptorFont(td1));
        return TextDescriptor.newTextDescriptor(mtd);
    }

    TextDescriptor makeDescriptor(int td0, int td1, int flags) {
        mtd.setCBits(td0, fixTextDescriptorFont(td1), flags);
        return TextDescriptor.newTextDescriptor(mtd);
    }

	/**
	 * Method to grab font associations that were stored on a Library.
	 * The font associations are used later to convert indices to true font names and numbers.
	 * @param associationArray array from FONT_ASSOCIATIONS variable.
	 */
	void setFontNames(String[] associationArray)
	{
		int maxAssociation = 0;
		for(int i=0; i<associationArray.length; i++)
		{
            if (associationArray[i] == null) continue;
			int fontNumber = TextUtils.atoi(associationArray[i]);
			if (fontNumber > maxAssociation) maxAssociation = fontNumber;
		}
		if (maxAssociation <= 0) return;

		fontNames = new String[maxAssociation];
		for(int i=0; i<maxAssociation; i++) fontNames[i] = null;
		for(int i=0; i<associationArray.length; i++)
		{
            if (associationArray[i] == null) continue;
			int fontNumber = TextUtils.atoi(associationArray[i]);
			if (fontNumber <= 0) continue;
			int slashPos = associationArray[i].indexOf('/');
			if (slashPos < 0) continue;
			fontNames[fontNumber-1] = associationArray[i].substring(slashPos+1);
		}
	}

	/**
	 * Method to convert the font number in a TextDescriptor to the proper value as
	 * cached in the Library.  The caching is examined by "getFontAssociationVariable()".
	 * @param descriptor1 value of descriptor1 from disk.
	 * @return patched value of descriptor1.
	 */
	private int fixTextDescriptorFont(int descriptor1)
	{
		int fontNumber = (descriptor1 & ELIBConstants.VTFACE) >> ELIBConstants.VTFACESH;
        if (fontNumber == 0) return descriptor1;
        descriptor1 &= ~ELIBConstants.VTFACE;
		if (fontNames != null && fontNumber <= fontNames.length)
        {
			String fontName = fontNames[fontNumber-1];
			TextDescriptor.ActiveFont af = TextDescriptor.ActiveFont.findActiveFont(fontName);
            if (af != null) {
                fontNumber = af.getIndex();
                if (fontNumber <= (ELIBConstants.VTFACE >> ELIBConstants.VTFACESH))
                    descriptor1 |= fontNumber << ELIBConstants.VTFACESH;
            }
		}
        return descriptor1;
	}

	/**
	 * Set line number for following errors and warnings.
	 * @param lineNumber line number for following errors and warnings.
	 */
	void setLineNumber(int lineNumber) {}

	/**
	 * Issue error message.
	 * @param message message string.
	 * @return MessageLog object for attaching further geometry details.
	 */
	void logError(String message)
	{
		errorCount++;
		System.out.println(message);
		errorLogger.logError(message, -1);
	}

	/**
	 * Issue warning message.
	 * @param message message string.
	 * @return MessageLog object for attaching further geometry details.
	 */
	void logWarning(String message)
	{
		System.out.println(message);
		errorLogger.logWarning(message, null, -1);
	}

	// *************************** THE CELL CLEANUP INTERFACE ***************************

	protected void computeTech(Cell cell, Set uncomputedCells)
	{
		uncomputedCells.remove(cell);
	}

	protected double computeLambda(Cell cell, int cellIndex) { return 1; }

	protected boolean spreadLambda(Cell cell, int cellIndex) { return false; }

	protected boolean canScale() { return false; }

	/**
	 * Method to recursively create the contents of each cell in the library.
	 */
	abstract void realizeCellsRecursively(Cell cell, HashSet<Cell> recursiveSetupFlag, HashSet<Cell> patchedCells, String scaledCellName, double scale);
}
