/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: JelibParser.java
 * Input/output tool: JELIB Library input
 * Written by Steven M. Rubin, Sun Microsystems.
 *
 * Copyright (c) 2004, 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.io.input;

import com.sun.electric.database.ImmutableArcInst;
import com.sun.electric.database.ImmutableNodeInst;
import com.sun.electric.database.Snapshot;
import com.sun.electric.database.geometry.EPoint;
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.ExportId;
import com.sun.electric.database.id.IdManager;
import com.sun.electric.database.id.LibId;
import com.sun.electric.database.id.NodeProtoId;
import com.sun.electric.database.id.PortProtoId;
import com.sun.electric.database.id.PrimitiveNodeId;
import com.sun.electric.database.id.PrimitivePortId;
import com.sun.electric.database.id.TechId;
import com.sun.electric.database.prototype.PortCharacteristic;
import com.sun.electric.database.text.CellName;
import com.sun.electric.database.text.Name;
import com.sun.electric.database.text.Version;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.variable.CodeExpression;
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.tool.Job;
import com.sun.electric.tool.Tool;
import com.sun.electric.tool.io.FileType;
import com.sun.electric.tool.ncc.basic.TransitiveRelation;
import com.sun.electric.tool.user.ErrorLogger;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.Orientation;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;

/**
 * This class reads files in new library file (.jelib) format.
 */
public class JelibParser
{
	class CellContents
	{
        final Version version;
		boolean filledIn;
		String fileName;
		int lineNumber;

        CellId cellId;
        CellName groupName;
        long creationDate;
        long revisionDate;
        TechId techId;
        boolean expanded;
        boolean allLocked;
        boolean instLocked;
        boolean cellLib;
        boolean techLib;
        Variable[] vars;

        List<NodeContents> nodes = new ArrayList<NodeContents>();
        List<ExportContents> exports = new ArrayList<ExportContents>();
        List<ArcContents> arcs = new ArrayList<ArcContents>();
		// map disk node names (duplicate node names written "sig"1 and "sig"2)
		HashMap<String,NodeContents> diskName = new HashMap<String,NodeContents>();

		CellContents(Version version)
		{
            this.version = version;
			filledIn = false;
		}
        
        void addNode(String diskNodeName, NodeContents nc) {
            nodes.add(nc);
            NodeContents old = diskName.put(diskNodeName, nc);
            if (old != null) {
                throw new IllegalArgumentException("Duplicate disk node name " + diskNodeName + " in cell " + cellId);
            }
        }
        
        AbstractNodeContents findNode(String diskNodeName) {
            return diskName.get(diskNodeName);
        }
        
        void addArc(ArcContents ac) {
            arcs.add(ac);
        }
        
        void addExport(ExportContents ec) {
            exports.add(ec);
        }
	}

    static abstract class AbstractNodeContents {
        abstract PortProtoId newPortId(String diskNodeName, String portName);
    }
    
    static class NodeContents extends AbstractNodeContents {
        int line;
        NodeProtoId protoId;
        String nodeName;
        TextDescriptor nameTextDescriptor;
        EPoint anchor;
        Orientation orient;
        EPoint size;
        TextDescriptor protoTextDescriptor;
        int flags;
        int techBits;
        Variable[] vars;
        
        @Override
        PortProtoId newPortId(String diskNodeName, String portName) {
            return protoId.newPortId(portName);
        }

        ImmutableNodeInst n;
        NodeInst ni;
    }

    static class ExportContents {
        int line;
        ExportId exportId;
        String exportUserName;
        AbstractNodeContents originalNode;
        PortProtoId originalPort;
        TextDescriptor nameTextDescriptor;
        PortCharacteristic ch;
        boolean alwaysDrawn;
        boolean bodyOnly;
        Variable[] vars;
        Point2D pos;
    }

    static class ArcContents {
        int line;
        ArcProtoId arcProtoId;
        String arcName;
        TextDescriptor nameTextDescriptor;
        double diskWidth;
        AbstractNodeContents headNode;
        PortProtoId headPort;
        EPoint headPoint;
        AbstractNodeContents tailNode;
        PortProtoId tailPort;
        EPoint tailPoint;
        int angle;
        int flags;
        Variable[] vars;
    }

    // The parsing result
    Version version;
    Variable[] libVars;

    public final LinkedHashMap<LibId,String> externalLibIds = new LinkedHashMap<LibId,String>();
	final LinkedHashMap<CellId,Rectangle2D> externalCells = new LinkedHashMap<CellId,Rectangle2D>();
	final LinkedHashMap<ExportId,EPoint> externalExports = new LinkedHashMap<ExportId,EPoint>();
    final LinkedHashMap<TechId,Variable[]> techIds = new LinkedHashMap<TechId,Variable[]>();
    final LinkedHashMap<PrimitiveNodeId,Variable[]> primitiveNodeIds = new LinkedHashMap<PrimitiveNodeId,Variable[]>();
    final LinkedHashMap<PrimitivePortId,Variable[]> primitivePortIds = new LinkedHashMap<PrimitivePortId,Variable[]>();
    final LinkedHashMap<ArcProtoId,Variable[]> arcProtoIds = new LinkedHashMap<ArcProtoId,Variable[]>();
    final LinkedHashMap<String,Variable[]> tools = new LinkedHashMap<String,Variable[]>();

 	final LinkedHashMap<CellId,CellContents> allCells = new LinkedHashMap<CellId,CellContents>();
//    final LinkedHashSet<String> delibCellFiles = new LinkedHashSet<String>();

    /*---------------------------------------------------------------------*/

	private static String[] revisions =
	{
		// Revision 1
		"8.01aw",
		// Revision 2
		"8.04l",
	};

    private static final Version newDelibHeaderVersion = Version.parseVersion("8.04n");
    private static boolean FIX_BUG_3697 = true;

    private static int defaultArcFlags;
    static {
        defaultArcFlags = ImmutableArcInst.DEFAULT_FLAGS;
        defaultArcFlags = ImmutableArcInst.HARD_SELECT.set(defaultArcFlags, false); // A
		defaultArcFlags	= ImmutableArcInst.BODY_ARROWED.set(defaultArcFlags, false); // B
        defaultArcFlags = ImmutableArcInst.FIXED_ANGLE.set(defaultArcFlags, true); // F
        defaultArcFlags = ImmutableArcInst.HEAD_NEGATED.set(defaultArcFlags, false); // G
        defaultArcFlags = ImmutableArcInst.HEAD_EXTENDED.set(defaultArcFlags, true); // I
        defaultArcFlags = ImmutableArcInst.TAIL_EXTENDED.set(defaultArcFlags, true); // J
        defaultArcFlags = ImmutableArcInst.TAIL_NEGATED.set(defaultArcFlags, false); // N
        defaultArcFlags = ImmutableArcInst.RIGID.set(defaultArcFlags, false); // R
        defaultArcFlags = ImmutableArcInst.SLIDABLE.set(defaultArcFlags, false); // S
        defaultArcFlags = ImmutableArcInst.HEAD_ARROWED.set(defaultArcFlags, false); // X
        defaultArcFlags = ImmutableArcInst.TAIL_ARROWED.set(defaultArcFlags, false); // Y
    }

    // Were in LibraryFiles
    final URL fileURL;
    private final FileType fileType;
    private final IdManager idManager;
    private final LibId libId;
    private final String filePath;
    private LineNumberReader lineReader;
    private final LineNumberReader delibHeaderReader;
    private int revision = revisions.length;
    private final ErrorLogger errorLogger;
    private final MutableTextDescriptor mtd = new MutableTextDescriptor();
    /** buffer for reading Variables. */                                    private final ArrayList<Variable> variablesBuf = new ArrayList<Variable>();

    // collect the cells by common protoName and by "groupLines" relation
    private final HashMap<String,ArrayList<CellContents>> cellsWithProtoName = new HashMap<String,ArrayList<CellContents>>();
    private final TransitiveRelation<String> transitiveProtoNames = new TransitiveRelation<String>();

    private HashMap<String,TextDescriptorAndCode> parsedDescriptorsF = new HashMap<String,TextDescriptorAndCode>();
    private HashMap<String,TextDescriptorAndCode> parsedDescriptorsT = new HashMap<String,TextDescriptorAndCode>();
//	private Version version;
	private char escapeChar = '\\';
	private String curLibName;
    private String curReadFile;

    private static class TextDescriptorAndCode {
        private final TextDescriptor td;
        private final CodeExpression.Code code;
        TextDescriptorAndCode(TextDescriptor td, CodeExpression.Code code) {
            this.td = td;
            this.code = code;
        }
    }

    private LibId curExternalLibId = null;
    private CellId curExternalCellId = null;
    private TechId curTechId = null;
    private PrimitiveNodeId curPrimId = null;

	JelibParser(LibId libId, URL fileURL, FileType fileType, boolean onlyProjectSettings, ErrorLogger errorLogger) throws IOException
	{
        idManager = libId.idManager;
        this.libId = libId;
        this.fileURL = fileURL;
        this.fileType = fileType;
        filePath = TextUtils.decodeString(fileURL.getFile()); // to decode possible encoded spaces
        this.errorLogger = errorLogger;

        InputStream inputStream;
        if (fileType == FileType.JELIB) {
            URLConnection urlCon = fileURL.openConnection();
            urlCon.setConnectTimeout(10000);
            urlCon.setReadTimeout(1000);
            curReadFile = filePath;
            inputStream = urlCon.getInputStream();
        } else if (fileType == FileType.DELIB) {
            curReadFile = filePath + File.separator + "header";
            try{
                inputStream = new FileInputStream(curReadFile);
            } catch (IOException e)
            {
                String message = "Header file " + curReadFile + " not found";
                System.out.println(message);
                throw new FileNotFoundException(message);
            }
        } else {
            throw new IllegalArgumentException("fileType");
        }
        InputStreamReader is = new InputStreamReader(inputStream, "UTF-8");
        this.lineReader = new LineNumberReader(is);
        delibHeaderReader = fileType == FileType.DELIB ? lineReader : null;
        try {
            readFromFile(onlyProjectSettings);
            collectCellGroups();
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            logError("Exception " + e.getMessage());
            throw new IOException(e);
        } finally {
            lineReader.close();
        }
	}

    private void collectCellGroups() {
        for (Iterator<Set<String>> git = transitiveProtoNames.getSetsOfRelatives(); git.hasNext(); ) {
            Set<String> protoNames = git.next();

            // Collect cells in this group
            ArrayList<CellContents> cellsInGroup = new ArrayList<CellContents>();
            for (String protoName: protoNames) {
                ArrayList<CellContents> list = cellsWithProtoName.get(protoName);
                if (list == null) {
                    logError("No cells for group name " + protoName);
                    continue;
                }
                cellsInGroup.addAll(list);
            }

            // Make cell group name
            ArrayList<CellName> cellNamesInGroup = new ArrayList<CellName>();
            for (CellContents cc: cellsInGroup)
                cellNamesInGroup.add(cc.cellId.cellName);
            CellName groupName = Snapshot.makeCellGroupName(cellNamesInGroup);

            // Set cell group name for each cell
            for (CellContents cc: cellsInGroup)
                cc.groupName = groupName;
        }
    }

    public static JelibParser parse(LibId libId, URL fileURL, FileType fileType, boolean onlyProjectSettings, ErrorLogger errorLogger) throws IOException {
        return new JelibParser(libId, fileURL, fileType, onlyProjectSettings, errorLogger);
    }

    private void readFromFile(boolean onlyProjectSettings) throws IOException {
        boolean ignoreCvsMergedContent = false;
		for(;;)
		{
			// get keyword from file
			String line = lineReader.readLine();
			if (line == null) break;

			// ignore blanks and comments
			if (line.length() == 0) continue;
			char first = line.charAt(0);
			if (first == '#') continue;

            if (line.startsWith("<<<<<<<")) {
                // This marks start of stuff merged from CVS, ignore this stuff
                ignoreCvsMergedContent = true;
                logError("CVS conflicts found: " + line);
                continue;
            }
            if (ignoreCvsMergedContent && line.startsWith("=======")) {
                // This marks start of local stuff merged, use this stuff
                ignoreCvsMergedContent = false;
                continue;
            }
            if (line.startsWith(">>>>>>>")) {
                // This marks end of cvs merging
                continue;
            }
            if (ignoreCvsMergedContent) continue;
            if (onlyProjectSettings && first != 'H' && first != 'O' && first != 'T') continue;

			if (first == 'C')
			{
                if (lineReader == delibHeaderReader) {
                    readDelibCell(line);
                } else {
                    readCell(line);
                }
                continue;
			}

			if (first == 'L')
			{
				// cross-library reference
				List<String> pieces = parseLine(line);
				if (pieces.size() != 2)
				{
					logError("External library declaration needs 2 fields: " + line);
					continue;
				}
                String libName = unQuote(pieces.get(0));
                curExternalLibId = idManager.newLibId(libName);
                String libFileName = unQuote(pieces.get(1));

				// recurse
                if (!externalLibIds.containsKey(curExternalLibId))
                    externalLibIds.put(curExternalLibId, libFileName);
				continue;
			}

			if (first == 'R') {
				// cross-library cell information
				List<String> pieces = parseLine(line);
 				int numPieces = revision == 1 ? 7 : 5;
 				if (pieces.size() != numPieces) {
					logError("External cell declaration needs " + numPieces + " fields: " + line);
					continue;
				}
				double lowX = readDouble(pieces.get(1));
				double highX = readDouble(pieces.get(2));
				double lowY = readDouble(pieces.get(3));
				double highY = readDouble(pieces.get(4));
				if (revision == 1)
				{
					Long.parseLong(pieces.get(5));		// ignore cdate
					Long.parseLong(pieces.get(6));		// ignore rdate
				}
				Rectangle2D bounds = new Rectangle2D.Double(lowX, lowY, highX-lowX, highY-lowY);
                String cellName = unQuote(pieces.get(0));
                curExternalCellId = curExternalLibId.newCellId(CellName.parseName(cellName));
                if (!externalCells.containsKey(curExternalCellId))
                    externalCells.put(curExternalCellId, bounds);
				continue;
			}

			if (first == 'F') {
				// cross-library export information
				List<String> pieces = parseLine(line);
				if (pieces.size() != 3) {
					logError("External export declaration needs 3 fields: " + line);
					continue;
				}
				String exportName = unQuote(pieces.get(0));
				double posX = readDouble(pieces.get(1));
				double posY = readDouble(pieces.get(1));
                ExportId exportId = curExternalCellId.newPortId(exportName);
				externalExports.put(exportId, EPoint.fromLambda(posX, posY));
				continue;
			}

			if (first == 'H') {
				// parse header
				List<String> pieces = parseLine(line);
				if (pieces.size() < 2) {
					logError("Library declaration needs 2 fields: " + line);
					continue;
				}
				version = Version.parseVersion(pieces.get(1));
				if (version == null) {
					logError("Badly formed version: " + pieces.get(1));
					continue;
				}
				for (revision = 0; revision < revisions.length; revision++) {
					if (version.compareTo(Version.parseVersion(revisions[revision])) < 0) break;
				}
				escapeChar = revision < 1 ? '^' : '\\';
				pieces = parseLine(line);
				curLibName = unQuote(pieces.get(0));
				if (version.compareTo(Version.getVersion()) > 0) {
					logWarning("Library " + curLibName + " comes from a NEWER version of Electric (" + version + ")");
				}
				libVars = readVariables(pieces, 2);
				continue;
			}

			if (first == 'O')
			{
				// parse Tool information
				List<String> pieces = parseLine(line);
				String toolName = unQuote(pieces.get(0));

				// get additional meaning preferences starting at position 1
                Variable[] vars = readVariables(pieces, 1);
                if (!tools.containsKey(toolName))
                    tools.put(toolName, vars);
				continue;
			}

			if (first == 'V') {
				// parse View information
				List<String> pieces = parseLine(line);
				String viewName = unQuote(pieces.get(0));
				View view = View.findView(viewName);
				if (view == null) {
					String viewAbbr = unQuote(pieces.get(1));
					view = View.newInstance(viewName, viewAbbr);
					if (view == null) {
						logError("Cannot create view " + viewName);
						continue;
					}
				}

				// get additional variables starting at position 2
                Variable[] vars = readVariables(pieces, 2);
				continue;
			}

			if (first == 'T') {
				// parse Technology information
				List<String> pieces = parseLine(line);
				String techName = unQuote(pieces.get(0));
                curTechId = idManager.newTechId(techName);
                curPrimId = null;

				// get additional meaning preferences  starting at position 1
                Variable[] vars = readVariables(pieces, 1);
                if (!techIds.containsKey(curTechId))
                    techIds.put(curTechId, vars);
				continue;
			}

			if (first == 'D') {
				// parse PrimitiveNode information
				List<String> pieces = parseLine(line);
				String primName = unQuote(pieces.get(0));
				if (curTechId == null) {
					logError("Primitive node " + primName + " has no technology before it");
					continue;
				}
                curPrimId = curTechId.newPrimitiveNodeId(primName);

				// get additional variables starting at position 1
                Variable[] vars = readVariables(pieces, 1);
                if (!primitiveNodeIds.containsKey(curPrimId))
                    primitiveNodeIds.put(curPrimId, vars);
				continue;
			}

			if (first == 'P') {
				// parse PrimitivePort information
				List<String> pieces = parseLine(line);
				String primPortName = unQuote(pieces.get(0));
				if (curPrimId == null) {
					logError("Primitive port " + primPortName + " has no primitive node before it");
					continue;
				}
                PrimitivePortId primitivePortId = curPrimId.newPortId(primPortName);

				// get additional variables starting at position 1
                Variable[] vars = readVariables(pieces, 1);
                if (!primitivePortIds.containsKey(primitivePortId))
                    primitivePortIds.put(primitivePortId, vars);
				continue;
			}

			if (first == 'W') {
				// parse ArcProto information
				List<String> pieces = parseLine(line);
				String arcName = unQuote(pieces.get(0));
				if (curTechId == null) {
					logError("Primitive arc " + arcName + " has no technology before it");
					continue;
				}
                ArcProtoId arcProtoId = curTechId.newArcProtoId(arcName);

				// get additional variables starting at position 1
                Variable[] vars = readVariables(pieces, 1);
                if (!arcProtoIds.containsKey(arcProtoId))
                    arcProtoIds.put(arcProtoId, vars);
				continue;
			}

			if (first == 'G')
			{
				// group information
				List<String> pieces = parseLine(line);
                String firstProtoName = null;
				for(int i=0; i<pieces.size(); i++)
				{
					String cellNameString = unQuote(pieces.get(i));
					if (cellNameString.length() == 0) continue;
					int colonPos = cellNameString.indexOf(':');
					if (colonPos >= 0) cellNameString = cellNameString.substring(colonPos+1);
                    CellName cellName = CellName.parseName(cellNameString);
					if (cellName == null) {
						logError("Bad cell name " + cellNameString);
						continue;
					}
                    if (cellsWithProtoName.get(cellName.getName()) == null) {
 						logError("Unknown cell " + cellName);
						continue;
                    }
                    String protoName = cellName.getName();
                    if (firstProtoName == null)
                        firstProtoName = protoName;
                    else
                        transitiveProtoNames.theseAreRelated(firstProtoName, protoName);
				}
				continue;
			}

			logError("Unrecognized line: " + line);
		}
	}

    private void readDelibCell(String line) throws IOException {
        // get the file location; remove 'C' at start
        String cellFile = line.substring(1, line.length());

        // New header file as of version 8.04n, no cell refs, searches delib dir for cell files
        if (version.compareTo(newDelibHeaderVersion) >= 0) {
            if (cellFile.equals(com.sun.electric.tool.io.output.DELIB.SEARCH_FOR_CELL_FILES)) {
                File delibDir = new File(filePath);
                if (delibDir.exists() && delibDir.isDirectory()) {
                    for (File file : delibDir.listFiles()) {
                        if (file.isDirectory()) continue;
                        String name = file.getName();
                        if (file.isHidden()) continue; // should skip AppleDouble encoded Macintosh file for example
                        int dot = name.lastIndexOf('.');
                        if (dot < 0) continue;
                        View view = View.findView(name.substring(dot+1));
                        if (view == null) // check if it is an extension with dot in between
                        {
                            int dot1 = name.indexOf('.');
                            if (dot1 != dot)
                            	view = View.findView(name.substring(dot1+1));
                        }
                        if (view == null) continue;
                        try {
                            readDelibFile(file);
                        } catch (Exception e) {
                        	if (Job.getDebug())
                        		e.printStackTrace();
                            if (e instanceof IOException) throw (IOException)e;
                            // some other exception, probably invalid cell file
                            Input.errorLogger.logError("Exception reading file "+file, -1);
                        }
                    }
                }
            }
            return;
        }

        cellFile = cellFile.replace(com.sun.electric.tool.io.output.DELIB.PLATFORM_INDEPENDENT_FILE_SEPARATOR, File.separatorChar);
        cellFile = cellFile.replace(File.separatorChar, ':');
        File cellFD = new File(filePath, cellFile);
        readDelibFile(cellFD);
    }

    private void readDelibFile(File cellFD) throws IOException {

        LineNumberReader cellReader;
        try {
            FileInputStream fin = new FileInputStream(cellFD);
            InputStreamReader is = new InputStreamReader(fin);
            cellReader = new LineNumberReader(is);
        } catch (IOException e) {
            System.out.println("Error opening file "+cellFD+": "+e.getMessage());
            return;
        }
        Version savedVersion = version;
        int savedRevision = revision;
        char savedEscapeChar = escapeChar;
        String savedCurLibName = curLibName;
        lineReader = cellReader;
        curReadFile = cellFD.getAbsolutePath();
        try {
            readFromFile(false);
//            delibCellFiles.add(curReadFile);
        } finally {
            version = savedVersion;
            revision = savedRevision;
            escapeChar = savedEscapeChar;
            curLibName = savedCurLibName;
            lineReader.close();
            lineReader = delibHeaderReader;
            curReadFile = filePath;
        }
    }

    CellContents newCellContents(Version version) {
        return new CellContents(version);
    }
    
    private void readCell(String line) throws IOException {
        // grab a cell description
        List<String> pieces = parseLine(line);
        int numPieces = revision >= 2 ? 6 : revision == 1 ? 5 : 7;
        if (pieces.size() < numPieces)
        {
            logError("Cell declaration needs " + numPieces + " fields: " + line);
            return;
        }
        int fieldIndex = 0;
        String name;
        String groupName = null;
        if (revision >= 1) {
            name = unQuote(pieces.get(fieldIndex++));
            if (revision >= 2) {
                String s = pieces.get(fieldIndex++);
                if (s.length() > 0)
                    groupName = unQuote(s);
            }
        } else {
            name = unQuote(pieces.get(fieldIndex++));
            String viewAbbrev = pieces.get(fieldIndex++);
            String versionString = pieces.get(fieldIndex++);
            name = name + ";" + versionString + "{" + viewAbbrev + "}";
        }
        CellName cellName = CellName.parseName(name);
        CellContents cc = newCellContents(version);
        cc.fileName = curReadFile;
        cc.lineNumber = lineReader.getLineNumber() + 1;
        cc.cellId = libId.newCellId(cellName);
        String techName = unQuote(pieces.get(fieldIndex++));
        cc.techId = idManager.newTechId(techName);
        cc.creationDate = Long.parseLong(pieces.get(fieldIndex++));
        cc.revisionDate = Long.parseLong(pieces.get(fieldIndex++));

        // parse state information
        String stateInfo = pieces.get(fieldIndex++);
        for(int i=0; i<stateInfo.length(); i++)
        {
            switch (stateInfo.charAt(i)) {
                case 'E': cc.expanded = true; break;
                case 'L': cc.allLocked = true; break;
                case 'I': cc.instLocked = true; break;
                case 'C': cc.cellLib = true; break;
                case 'T': cc.techLib = true; break;
            }
        }

        // add variables
        assert fieldIndex == numPieces;
        cc.vars = readVariables(pieces, numPieces);

        // gather the contents of the cell
        for(;;)
        {
            String nextLine = lineReader.readLine();
            if (nextLine == null) break;
            if (nextLine.length() == 0) continue;
            char nextFirst = nextLine.charAt(0);
            if (nextFirst == 'X') break;
            switch (nextFirst) {
                case '#':
                    break;
                case 'N':
                case 'I':
                    parseNode(nextLine, cc);
                    break;
                case 'E':
                    parseExport(nextLine, cc);
                    break;
                case 'A':
                    parseArc(nextLine, cc);
                    break;
                default:
            }
        }

        // check if the version is not null
        if (cc.version == null)
        {
            logError("Version for Cell '" + cc.cellId.cellName + "' is null");
            return;
        }

        // remember the contents of the cell for later
        if (allCells.containsKey(cc.cellId)) {
            CellContents cont = allCells.get(cc.cellId);
            String msg = "Duplicate cell ." + cc.cellId + "'. Previously found in file '" + cont.fileName + "'";
            logError(msg);
            throw new IOException(msg);
//            return;
        }
        String protoName = cellName.getName();
        if (groupName == null)
            groupName = protoName;
        transitiveProtoNames.theseAreRelated(protoName, groupName);
        allCells.put(cc.cellId, cc);
        ArrayList<CellContents> list = cellsWithProtoName.get(protoName);
        if (list == null) {
            list = new ArrayList<CellContents>();
            cellsWithProtoName.put(protoName, list);
        }
        list.add(cc);
        return;
    }

    private void parseNode(String cellString, CellContents cc) {
        NodeContents n = new NodeContents();
        n.line = lineReader.getLineNumber();

        // parse the node line
        List<String> pieces = parseLine(cellString);
        char firstChar = cellString.charAt(0);
        int numPieces = revision < 1 ? 10 : firstChar == 'N' ? 9 : 8;
        if (pieces.size() < numPieces)
        {
            logError("Node instance needs " + numPieces + " fields: " + cellString, cc.cellId);
            return;
        }
        String protoName = unQuote(pieces.get(0));
        // figure out the name for this node.  Handle the form: "Sig"12
        String diskNodeName = revision >= 1 ? pieces.get(1) : unQuote(pieces.get(1));
        String nodeName = diskNodeName;
        if (nodeName.charAt(0) == '"')
        {
            int lastQuote = nodeName.lastIndexOf('"');
            if (lastQuote > 1)
            {
                if (FIX_BUG_3697) {
                    if (revision >= 1) {
                        nodeName = unQuote(nodeName.substring(0, lastQuote + 1));
                    } else {
                        nodeName = nodeName.substring(1, lastQuote);
                    }
                } else { 
                    nodeName = nodeName.substring(1, lastQuote);
                    if (revision >= 1) nodeName = unQuote(nodeName);
                }
            }
        }
        n.nodeName = nodeName;
        String nameTextDescriptorInfo = pieces.get(2);
        double x = readDouble(pieces.get(3));
        double y = readDouble(pieces.get(4));

        LibId libId = cc.cellId.libId;
        String prefixName = libId.libName;
        int colonPos = protoName.indexOf(':');
        if (colonPos < 0)
        {
            if (firstChar == 'I' || revision < 1)
                n.protoId = libId.newCellId(CellName.parseName(protoName));
            else
                n.protoId = cc.techId.newPrimitiveNodeId(protoName);
        } else
        {
            prefixName = protoName.substring(0, colonPos);
            protoName = protoName.substring(colonPos+1);
            if (firstChar == 'I' || revision < 1 && protoName.indexOf('{') >= 0) {
                if (!prefixName.equals(curLibName))
                    libId = idManager.newLibId(prefixName);
                n.protoId = libId.newCellId(CellName.parseName(protoName));
            } else {
                n.protoId = idManager.newTechId(prefixName).newPrimitiveNodeId(protoName);
            }
        }

        n.size = EPoint.ORIGIN;
        boolean flipX = false, flipY = false;
        String orientString;
        String stateInfo;
        String textDescriptorInfo = "";
        if (firstChar == 'N' || revision < 1)
        {
            double wid = readDouble(pieces.get(5));
            if (revision < 1 && (wid < 0 || wid == 0 && 1/wid < 0)) {
                flipX = true;
                wid = -wid;
            }
            double hei = readDouble(pieces.get(6));
            if (revision < 1 && (hei < 0 || hei == 0 && 1/hei < 0)) {
                flipY = true;
                hei = -hei;
            }
            if (n.protoId instanceof PrimitiveNodeId)
                n.size = EPoint.fromLambda(wid, hei);
            orientString = pieces.get(7);
            stateInfo = pieces.get(8);
            if (revision < 1)
                textDescriptorInfo = pieces.get(9);
        } else
        {
            orientString = pieces.get(5);
            stateInfo = pieces.get(6);
            textDescriptorInfo = pieces.get(7);
        }
        int angle = 0;
        for (int i = 0; i < orientString.length(); i++)
        {
            char ch = orientString.charAt(i);
            if (ch == 'X') flipX = !flipX;
            else if (ch == 'Y')	flipY = !flipY;
            else if (ch == 'R') angle += 900;
            else
            {
                angle += Integer.valueOf(orientString.substring(i));
                break;
            }
        }

        // parse state information in stateInfo field
        TextDescriptorAndCode nameTdC = loadTextDescriptor(nameTextDescriptorInfo, false);
        n.nameTextDescriptor = nameTdC.td;
        int flags = 0, techBits = 0;
        // parse state information in jelibUserBits
        parseStateInfo:
        for(int i=0; i<stateInfo.length(); i++) {
            char chr = stateInfo.charAt(i);
            switch (chr) {
                case 'E': /*flags = ImmutableNodeInst.EXPAND.set(flags, true);*/ break;
                case 'L': flags = ImmutableNodeInst.LOCKED.set(flags, true); break;
                case 'S': /*userBits |= NSHORT;*/ break; // deprecated
                case 'V': flags = ImmutableNodeInst.VIS_INSIDE.set(flags, true); break;
                case 'W': /*flags = ImmutableNodeInst.WIPED.set(flags, true);*/ break; // deprecated
                case 'A': flags = ImmutableNodeInst.HARD_SELECT.set(flags, true); break;
                default:
                    if (Character.isDigit(chr)) {
                        stateInfo = stateInfo.substring(i);
                        try {
                            techBits = Integer.parseInt(stateInfo);
                        } catch (NumberFormatException e) {
                            logError("(" + cc.cellId + ") bad node bits" + stateInfo, cc.cellId);
                        }
                        break parseStateInfo;
                    }
            }
        }
        n.flags = flags;
        n.techBits = techBits;
        TextDescriptorAndCode protoTdC = loadTextDescriptor(textDescriptorInfo, false);
        n.protoTextDescriptor = protoTdC.td;

        // create the node
        n.orient = Orientation.fromJava(angle, flipX, flipY);
        n.anchor = EPoint.fromLambda(x, y);

        // add variables in fields 10 and up
        n.vars = readVariables(pieces, numPieces);
        // insert into map of disk names
        cc.addNode(diskNodeName, n);
    }

    private void parseExport(String cellString, CellContents cc) {
        ExportContents e = new ExportContents();
        e.line = lineReader.getLineNumber();

        // parse the export line
        List<String> pieces = parseLine(cellString);
        if (revision >= 2 && pieces.size() == 1) {
            // Unused ExportId
            String exportName = unQuote(pieces.get(0));
            cc.cellId.newPortId(exportName);
            return;
        }
        int numPieces = revision >= 2 ? 6 : revision == 1 ? 5 : 7;
        if (pieces.size() < numPieces)
        {
            logError("Export needs " + numPieces + " fields, has " + pieces.size() + ": " + cellString, cc.cellId);
            return;
        }
        int fieldIndex = 0;
        String exportName = unQuote(pieces.get(fieldIndex++));
        String exportUserName = null;
        if (revision >= 2) {
            String s = pieces.get(fieldIndex++);
            if (s.length() != 0)
                exportUserName = unQuote(s);
        }
        if (exportUserName == null || exportName.equals(exportUserName))
            exportName = Name.findName(exportName).toString(); // save memory using String from Name
        e.exportId = cc.cellId.newPortId(exportName);
        e.exportUserName = exportUserName;
        // get text descriptor in field 1
        String textDescriptorInfo = pieces.get(fieldIndex++);
        String nodeName = revision >= 1 ? pieces.get(fieldIndex++) : unQuote(pieces.get(fieldIndex++));
        e.originalNode = cc.findNode(nodeName);
        String portName = unQuote(pieces.get(fieldIndex++));
        if (e.originalNode == null)
        {
        	logError("Can't find nodename " + nodeName + " in cell '" + cellString + "'", cc.cellId);
            return;
        }
        e.originalPort = e.originalNode.newPortId(nodeName, portName);
        Point2D pos = null;
        if (revision < 1)
        {
            double x = readDouble(pieces.get(fieldIndex++));
            double y = readDouble(pieces.get(fieldIndex++));
            pos = new Point2D.Double(x, y);
        }
        e.pos = pos;
        // parse state information in field 6
        String userBits = pieces.get(fieldIndex++);
        assert fieldIndex == numPieces;

        TextDescriptorAndCode nameTdC =  loadTextDescriptor(textDescriptorInfo, false);
        e.nameTextDescriptor = nameTdC.td;
        // parse state information
        int slashPos = userBits.indexOf('/');
        if (slashPos >= 0) {
            String extras = userBits.substring(slashPos);
            userBits = userBits.substring(0, slashPos);
            while (extras.length() > 0) {
                switch (extras.charAt(1)) {
                    case 'A': e.alwaysDrawn = true; break;
                    case 'B': e.bodyOnly = true; break;
                }
                extras = extras.substring(2);
            }
        }
        PortCharacteristic ch = PortCharacteristic.findCharacteristicShort(userBits);
        e.ch = ch != null ? ch : PortCharacteristic.UNKNOWN;

        // add variables in tail fields
        e.vars = readVariables(pieces, numPieces);
        cc.addExport(e);
    }

    private void parseArc(String cellString, CellContents cc) {
        ArcContents a = new ArcContents();
        a.line = lineReader.getLineNumber();

        // parse the arc line
        List<String> pieces = parseLine(cellString);
        if (pieces.size() < 13)
        {
            logError("Arc instance needs 13 fields: " + cellString, cc.cellId);
            return;
        }
        TechId techId = cc.techId;
        String protoName = unQuote(pieces.get(0));
        int indexOfColon = protoName.indexOf(':');
        if (indexOfColon >= 0) {
            techId = idManager.newTechId(protoName.substring(0, indexOfColon));
            protoName = protoName.substring(indexOfColon + 1);
        }
        a.arcProtoId = techId.newArcProtoId(protoName);
        String diskArcName = revision >= 1 ? pieces.get(1) : unQuote(pieces.get(1));
        String arcName = diskArcName;
        if (arcName.charAt(0) == '"')
        {
            int lastQuote = arcName.lastIndexOf('"');
            if (lastQuote > 1)
            {
                if (FIX_BUG_3697) {
                    if (revision >= 1) {
                        arcName = unQuote(arcName.substring(0, lastQuote + 1));
                    } else {
                        arcName = arcName.substring(1, lastQuote);
                    }
                } else { 
                    arcName = arcName.substring(1, lastQuote);
                    if (revision >= 1) arcName = unQuote(arcName);
                }
            }
        }
        a.arcName = arcName;
        a.diskWidth = readDouble(pieces.get(3));

        String headNodeName = revision >= 1 ? pieces.get(5) : unQuote(pieces.get(5));
        String headPortName = unQuote(pieces.get(6));
        double headX = readDouble(pieces.get(7));
        double headY = readDouble(pieces.get(8));
        a.headNode = cc.findNode(headNodeName);
        if (a.headNode == null)
        {
        	logError("Arc node '" + headNodeName + "' no found in arc " + arcName);
        	return;
        }
        a.headPort = a.headNode.newPortId(headNodeName, headPortName);
        a.headPoint = EPoint.fromLambda(headX, headY);

        String tailNodeName = revision >= 1 ? pieces.get(9) : unQuote(pieces.get(9));
        String tailPortName = unQuote(pieces.get(10));
        double tailX = readDouble(pieces.get(11));
        double tailY = readDouble(pieces.get(12));
        a.tailNode = cc.findNode(tailNodeName);
        if (a.tailNode == null)
        {
        	logError("Arc node '" + tailNodeName + "' no found in arc " + arcName);
        	return;
        }
        a.tailPort = a.tailNode.newPortId(tailNodeName, tailPortName);
        a.tailPoint = EPoint.fromLambda(tailX, tailY);

        // parse state information in field 4
        String stateInfo = pieces.get(4);
        boolean extended = true, directional = false, reverseEnds = false,
            skipHead = false, skipTail = false,
            tailNotExtended = false, headNotExtended = false,
            tailArrowed = false, headArrowed = false, bodyArrowed = false;
        // Default flags are false except FIXED_ANGLE
        // SLIDABLE
        int flags = defaultArcFlags;
        int angle = ArcInst.DEFAULTANGLE;
        parseStateInfo:
        for(int i=0; i<stateInfo.length(); i++)
        {
            char chr = stateInfo.charAt(i);
            switch (chr) {
                case 'R': flags = ImmutableArcInst.RIGID.set(flags, true); break;
                case 'F': flags = ImmutableArcInst.FIXED_ANGLE.set(flags, false); break;
                case 'S': flags = ImmutableArcInst.SLIDABLE.set(flags, true); break;
                case 'A': flags = ImmutableArcInst.HARD_SELECT.set(flags, true); break;
                case 'N': flags = ImmutableArcInst.TAIL_NEGATED.set(flags, true); break;
                case 'G': flags = ImmutableArcInst.HEAD_NEGATED.set(flags, true); break;
                case 'X': headArrowed = true; break;
                case 'Y': tailArrowed = true; break;
                case 'B': bodyArrowed = true; break;
                case 'I': headNotExtended = true; break;
                case 'J': tailNotExtended = true; break;

                // deprecated
                case 'E': extended = false; break;
                case 'D': directional = true; break;
                case 'V': reverseEnds = true; break;
                case 'H': skipHead = true; break;
                case 'T': skipTail = true; break;
                default:
                    if ('0' <= chr && chr <= '9')
                    {
                        angle = Integer.valueOf(stateInfo.substring(i));
                        break parseStateInfo;
                    }
            }
        }

        // if old bits were used, convert them
        if (!extended || directional)
        {
            if (!extended) headNotExtended = tailNotExtended = true;
            if (directional)
            {
                if (reverseEnds) tailArrowed = true; else
                    headArrowed = true;
                bodyArrowed = true;
            }
            if (skipHead) headArrowed = headNotExtended = false;
            if (skipTail) tailArrowed = tailNotExtended = false;
        }

        // set the bits
        flags = ImmutableArcInst.HEAD_EXTENDED.set(flags, !headNotExtended);
        flags = ImmutableArcInst.TAIL_EXTENDED.set(flags, !tailNotExtended);
        flags = ImmutableArcInst.HEAD_ARROWED.set(flags, headArrowed);
        flags = ImmutableArcInst.TAIL_ARROWED.set(flags, tailArrowed);
        flags = ImmutableArcInst.BODY_ARROWED.set(flags, bodyArrowed);
        a.angle = angle;
        a.flags = flags;

        // get the ard name text descriptor
        String nameTextDescriptorInfo = pieces.get(2);
        TextDescriptorAndCode nameTdC = loadTextDescriptor(nameTextDescriptorInfo, false);
        a.nameTextDescriptor = nameTdC.td;

        // add variables in fields 13 and up
        a.vars = readVariables(pieces, 13);
        cc.addArc(a);
    }

	/**
	 * Method to parse a line from the file, breaking it into a List of Strings.
	 * Each field in the file is separated by "|".
	 * Quoted strings are handled properly, as are the escape character.
	 * @param line the text from the file.
	 * @return a List of Strings.
	 */
	private List<String> parseLine(String line)
	{
		List<String> stringPieces = new ArrayList<String>();
		int len = line.length();
		int pos = 1;
		int startPos = 1;
		boolean inQuote = false;
		while (pos < len)
		{
			char chr = line.charAt(pos++);
			if (chr == escapeChar)
			{
				pos++;
				continue;
			}
			if (chr == '"') inQuote = !inQuote;
			if (chr == '|' && !inQuote)
			{
				stringPieces.add(line.substring(startPos, pos - 1));
				startPos = pos;
			}
		}
		if (pos > len) pos = len;
		stringPieces.add(line.substring(startPos, pos));
		return stringPieces;
	}

	private String unQuote(String line)
	{
		int len = line.length();
		if (revision >= 1)
		{
			if (len < 2 || line.charAt(0) != '"') return line;
			int lastQuote = line.lastIndexOf('"');
			if (lastQuote != len - 1)
				return unQuote(line.substring(0, lastQuote + 1)) + line.substring(lastQuote + 1);
			line = line.substring(1, len - 1);
			len -= 2;
		} else
		{
			if (line.indexOf(escapeChar) < 0) return line;
		}
		StringBuffer sb = new StringBuffer();
		assert len == line.length();
		for(int i=0; i<len; i++)
		{
			char chr = line.charAt(i);
			if (chr == escapeChar)
			{
				i++;
				if (i >= len) break;
				chr = line.charAt(i);
				if (chr == 'n' && revision >= 1) chr = '\n';
				if (chr == 'r' && revision >= 1) chr = '\r';
			}
			sb.append(chr);
		}
		return sb.toString();
	}

	/**
	 * Method to read variables to an ElectricObject from a List of strings.
	 * @param pieces the array of Strings that described the ElectricObject.
	 * @param position the index in the array of strings where Variable descriptions begin.
     * @return an array of Variables.
	 */
	private Variable[] readVariables(List<String> pieces, int position)
	{
        variablesBuf.clear();
		int total = pieces.size();
		for(int i=position; i<total; i++)
		{
			String piece = pieces.get(i);
			int openPos = 0;
			boolean inQuote = false;
			for(; openPos < piece.length(); openPos++)
			{
				char chr = piece.charAt(openPos);
				if (chr == escapeChar) { openPos++;   continue; }
				if (chr == '"') inQuote = !inQuote;
				if (chr == '(' && !inQuote) break;
			}
			if (openPos >= piece.length())
			{
				logError("Badly formed variable (no open parenthesis): " + piece);
				continue;
			}
			String varName = unQuote(piece.substring(0, openPos));
			Variable.Key varKey = Variable.newKey(varName);
			int closePos = piece.indexOf(')', openPos);
			if (closePos < 0)
			{
				logError("Badly formed variable (no close parenthesis): " + piece);
				continue;
			}
			String varBits = piece.substring(openPos+1, closePos);
			int objectPos = closePos + 1;
			if (objectPos >= piece.length())
			{
				logError("Variable type missing: " + piece);
				continue;
			}
			char varType = piece.charAt(objectPos++);
			switch (varType)
			{
				case 'B':
				case 'C':
				case 'D':
				case 'E':
				case 'F':
				case 'G':
				case 'H':
				case 'I':
				case 'L':
				case 'O':
				case 'P':
				case 'R':
				case 'S':
				case 'T':
				case 'V':
				case 'Y':
					break; // break from switch
				default:
					logError("Variable type invalid: " + piece);
					continue; // continue loop
			}
			Object obj = null;
			if (objectPos >= piece.length())
			{
				continue;
			} else
			{
				if (piece.charAt(objectPos) == '[')
				{
					List<Object> objList = new ArrayList<Object>();
					objectPos++;
					while (objectPos < piece.length())
					{
						int start = objectPos;
						inQuote = false;
						while (objectPos < piece.length())
						{
							if (inQuote)
							{
								if (piece.charAt(objectPos) == escapeChar)
								{
									objectPos++;
								} else if (piece.charAt(objectPos) == '"')
								{
									inQuote = false;
								}
								objectPos++;
								continue;
							}
							if (piece.charAt(objectPos) == ',' || piece.charAt(objectPos) == ']') break;
							if (piece.charAt(objectPos) == '"')
							{
								inQuote = true;
							}
							objectPos++;
						}
						Object oneObj = getVariableValue(piece.substring(start, objectPos), varType);
						objList.add(oneObj);
						if (piece.charAt(objectPos) == ']') break;
						objectPos++;
					}
					if (objectPos >= piece.length())
					{
						logError("Badly formed array (no closed bracket): " + piece);
						continue;
					}
					else if (objectPos < piece.length() - 1)
					{
						logError("Badly formed array (extra characters after closed bracket): " + piece);
						continue;
					}
					int limit = objList.size();
					Object [] objArray;
					switch (varType)
					{
						case 'B': objArray = new Boolean[limit];        break;
						case 'C': objArray = new CellId[limit];         break;
						case 'D': objArray = new Double[limit];         break;
						case 'E': objArray = new ExportId[limit];       break;
						case 'F': objArray = new Float[limit];          break;
						case 'G': objArray = new Long[limit];           break;
						case 'H': objArray = new Short[limit];          break;
						case 'I': objArray = new Integer[limit];        break;
						case 'L': objArray = new LibId[limit];          break;
						case 'O': objArray = new Tool[limit];           break;
						case 'P': objArray = new PrimitiveNodeId[limit];break;
						case 'R': objArray = new ArcProtoId[limit];     break;
						case 'S': objArray = new String[limit];         break;
						case 'T': objArray = new TechId[limit];         break;
						case 'V': objArray = new EPoint[limit];         break;
						case 'Y': objArray = new Byte[limit];           break;
                        default: throw new AssertionError();
					}
					for(int j=0; j<limit; j++)
						objArray[j] = objList.get(j);
					obj = objArray;
				} else
				{
					// a scalar Variable
					obj = getVariableValue(piece.substring(objectPos), varType);
                    if (obj == null) {
                        // ????
                        continue;
                    }
				}
			}

			// create the variable
            TextDescriptorAndCode tdc = loadTextDescriptor(varBits, true);
            obj = Variable.withCode(obj, tdc.code);
            Variable d = Variable.newInstance(varKey, obj, tdc.td);
            variablesBuf.add(d);
		}
        return variablesBuf.toArray(Variable.NULL_ARRAY);
	}

	/**
	 * Method to load a TextDescriptor from a String description of it.
	 * @param varBits the String that describes the TextDescriptor.
     * @param onVar true if this TextDescriptor resides on a Variable
	 * It may be false if the TextDescriptor is on a NodeInst or Export.
	 * @return loaded TextDescriptor
	 */
	private TextDescriptorAndCode loadTextDescriptor(String varBits, boolean onVar)
	{
        HashMap<String,TextDescriptorAndCode> parsedDescriptors = onVar ? parsedDescriptorsT : parsedDescriptorsF;
        TextDescriptorAndCode tdc = parsedDescriptors.get(varBits);
        if (tdc != null) return tdc;

        boolean error = false;
        mtd.setCBits(0, 0, 0);
        CodeExpression.Code code = CodeExpression.Code.NONE;
        if (!onVar) mtd.setDisplay(TextDescriptor.Display.SHOWN);
		double xoff = 0, yoff = 0;
		for(int j=0; j<varBits.length(); j++)
		{
			char varBit = varBits.charAt(j);
			switch (varBit)
			{
				case 'D':		// display position
				case 'd':		// display position
					mtd.setDisplay(varBit == 'D' ? TextDescriptor.Display.SHOWN : TextDescriptor.Display.HIDDEN);
					j++;
					if (j >= varBits.length())
					{
						logError("Incorrect display specification: " + varBits);
                        error = true;
						break;
					}
					switch (varBits.charAt(j))
					{
						case '5': mtd.setPos(TextDescriptor.Position.CENT);       break;
						case '8': mtd.setPos(TextDescriptor.Position.UP);         break;
						case '2': mtd.setPos(TextDescriptor.Position.DOWN);       break;
						case '4': mtd.setPos(TextDescriptor.Position.LEFT);       break;
						case '6': mtd.setPos(TextDescriptor.Position.RIGHT);      break;
						case '7': mtd.setPos(TextDescriptor.Position.UPLEFT);     break;
						case '9': mtd.setPos(TextDescriptor.Position.UPRIGHT);    break;
						case '1': mtd.setPos(TextDescriptor.Position.DOWNLEFT);   break;
						case '3': mtd.setPos(TextDescriptor.Position.DOWNRIGHT);  break;
						case '0': mtd.setPos(TextDescriptor.Position.BOXED);      break;
					}
					break;
				case 'N':		// display type
					mtd.setDispPart(TextDescriptor.DispPos.NAMEVALUE);
					break;
				case 'A':		// absolute text size
					int semiPos = varBits.indexOf(';', j);
					if (semiPos < 0)
					{
						logError("Bad absolute size (semicolon missing): " + varBits);
                        error = true;
						break;
					}
					mtd.setAbsSize(Integer.valueOf(varBits.substring(j+1, semiPos)));
					j = semiPos;
					break;
				case 'G':		// relative text size
					semiPos = varBits.indexOf(';', j);
					if (semiPos < 0)
					{
						logError("Bad relative size (semicolon missing): " + varBits);
                        error = true;
						break;
					}
					mtd.setRelSize(readDouble(varBits.substring(j+1, semiPos)));
					j = semiPos;
					break;
				case 'X':		// X offset
					semiPos = varBits.indexOf(';', j);
					if (semiPos < 0)
					{
						logError("Bad X offset (semicolon missing): " + varBits);
                        error = true;
						break;
					}
					xoff = readDouble(varBits.substring(j+1, semiPos));
					j = semiPos;
					break;
				case 'Y':		// Y offset
					semiPos = varBits.indexOf(';', j);
					if (semiPos < 0)
					{
						logError("Bad Y offset (semicolon missing): " + varBits);
                        error = true;
						break;
					}
					yoff = readDouble(varBits.substring(j+1, semiPos));
					j = semiPos;
					break;
				case 'B':		// bold
					mtd.setBold(true);
					break;
				case 'I':		// italic
					mtd.setItalic(true);
					break;
				case 'L':		// underlined
					mtd.setUnderline(true);
					break;
				case 'F':		// font
					semiPos = varBits.indexOf(';', j);
					if (semiPos < 0)
					{
						logError("Bad font (semicolon missing): " + varBits);
                        error = true;
						break;
					}
					TextDescriptor.ActiveFont af = TextDescriptor.ActiveFont.findActiveFont(varBits.substring(j+1, semiPos));
                    if (af != null)
                        mtd.setFace(af.getIndex());
					j = semiPos;
					break;
				case 'C':		// color
					semiPos = varBits.indexOf(';', j);
					if (semiPos < 0)
					{
						logError("Bad color (semicolon missing): " + varBits);
                        error = true;
						break;
					}
					mtd.setColorIndex(Integer.valueOf(varBits.substring(j+1, semiPos)));
					j = semiPos;
					break;
				case 'R':		// rotation
					TextDescriptor.Rotation rot = TextDescriptor.Rotation.ROT90;
					if (j+1 < varBits.length() && varBits.charAt(j+1) == 'R')
					{
						rot = TextDescriptor.Rotation.ROT180;
						j++;
					}
					if (j+1 < varBits.length() && varBits.charAt(j+1) == 'R')
					{
						rot = TextDescriptor.Rotation.ROT270;
						j++;
					}
					mtd.setRotation(rot);
					break;
				case 'H':		// inheritable
					mtd.setInherit(true);
					break;
				case 'T':		// interior
					mtd.setInterior(true);
					break;
				case 'P':		// parameter
					mtd.setParam(true);
					break;
				case 'O':		// code
					j++;
					if (j >= varBits.length())
					{
						logError("Bad language specification: " + varBits);
                        error = true;
						break;
					}
					char codeLetter = varBits.charAt(j);
					if (!onVar)
					{
						logError("Illegal use of language specification: " + varBits);
						error = true;
						break;
					}
                    switch (codeLetter) {
                        case 'J': code = CodeExpression.Code.JAVA; break;
                        case 'L': code = CodeExpression.Code.SPICE; break;
                        case 'T': code = CodeExpression.Code.TCL; break;
                        default:
                            logError("Unknown language specification: " + varBits);
                            error = true;
                    }
					break;
				case 'U':		// units
					j++;
					if (j >= varBits.length())
					{
						logError("Bad units specification: " + varBits);
                        error = true;
						break;
					}
					char unitsLetter = varBits.charAt(j);
					if (unitsLetter == 'R') mtd.setUnit(TextDescriptor.Unit.RESISTANCE); else
					if (unitsLetter == 'C') mtd.setUnit(TextDescriptor.Unit.CAPACITANCE); else
					if (unitsLetter == 'I') mtd.setUnit(TextDescriptor.Unit.INDUCTANCE); else
					if (unitsLetter == 'A') mtd.setUnit(TextDescriptor.Unit.CURRENT); else
					if (unitsLetter == 'V') mtd.setUnit(TextDescriptor.Unit.VOLTAGE); else
					if (unitsLetter == 'D') mtd.setUnit(TextDescriptor.Unit.DISTANCE); else
					if (unitsLetter == 'T') mtd.setUnit(TextDescriptor.Unit.TIME); else
					{
						logError("Unknown units specification: " + varBits);
                        error = true;
					}
					break;
			}
		}
		mtd.setOff(xoff, yoff);
		TextDescriptor td = TextDescriptor.newTextDescriptor(mtd);
        tdc = new TextDescriptorAndCode(td, code);
        if (!error) parsedDescriptors.put(varBits, tdc);
        return tdc;
	}

	/**
	 * Method to convert a String to an Object so that it can be stored in a Variable.
	 * @param piece the String to be converted.
	 * @param varType the type of the object to convert (a letter from the file).
	 * @return the Object representation of the given String.
	 */
	private Object getVariableValue(String piece, char varType)
	{
		int colonPos;
		String libName;
		int secondColonPos;
		String cellName;
		int commaPos;

        if (piece.length() == 0)
            return null;
		if (revision >= 1)
			piece = unQuote(piece);
		switch (varType)
		{
			case 'B':		// Boolean
				return new Boolean(piece.charAt(0)=='T' ? true : false);
			case 'C':		// Cell (should delay analysis until database is built!!!)
				if (piece.length() == 0) return null;
				colonPos = piece.indexOf(':');
				if (colonPos < 0)
				{
					logError("Badly formed Cell (missing colon): " + piece);
					break;
				}
				libName = piece.substring(0, colonPos);
                LibId libId = idManager.newLibId(libName);
				cellName = piece.substring(colonPos+1);
				commaPos = cellName.indexOf(',');
				if (commaPos >= 0) cellName = cellName.substring(0, commaPos);
                return libId.newCellId(CellName.parseName(cellName));
			case 'D':		// Double
				return Double.valueOf(piece);
			case 'E':		// Export (should delay analysis until database is built!!!)
				colonPos = piece.indexOf(':');
				if (colonPos < 0)
				{
					logError("Badly formed Export (missing library colon): " + piece);
					break;
				}
				libName = piece.substring(0, colonPos);
                libId = idManager.newLibId(libName);
				secondColonPos = piece.indexOf(':', colonPos+1);
				if (secondColonPos < 0)
				{
					logError("Badly formed Export (missing cell colon): " + piece);
					break;
				}
				cellName = piece.substring(colonPos+1, secondColonPos);
                CellId cellId = libId.newCellId(CellName.parseName(cellName));
				String exportName = piece.substring(secondColonPos+1);
				commaPos = exportName.indexOf(',');
				if (commaPos >= 0) exportName = exportName.substring(0, commaPos);
                return cellId.newPortId(exportName);
			case 'F':		// Float
				return Float.valueOf(piece);
			case 'G':		// Long
				return Long.valueOf(piece);
			case 'H':		// Short
				return Short.valueOf(piece);
			case 'I':		// Integer
				return Integer.valueOf(piece);
			case 'L':		// Library (should delay analysis until database is built!!!)
				libName = piece;
				commaPos = libName.indexOf(',');
				if (commaPos >= 0) libName = libName.substring(0, commaPos);
				return idManager.newLibId(libName);
			case 'O':		// Tool
				String toolName = piece;
				commaPos = toolName.indexOf(',');
				if (commaPos >= 0) toolName = toolName.substring(0, commaPos);
				Tool tool = Tool.findTool(toolName);
				if (tool == null)
					logError("Unknown Tool: " + piece);
				return tool;
			case 'P':		// PrimitiveNodeId
				colonPos = piece.indexOf(':');
				if (colonPos < 0)
				{
					logError("Badly formed PrimitiveNode (missing colon): " + piece);
					break;
				}
				String techName = piece.substring(0, colonPos);
                TechId techId = idManager.newTechId(techName);
				String nodeName = piece.substring(colonPos+1);
				commaPos = nodeName.indexOf(',');
				if (commaPos >= 0) nodeName = nodeName.substring(0, commaPos);
				return techId.newPrimitiveNodeId(nodeName);
			case 'R':		// ArcProtoId
				colonPos = piece.indexOf(':');
				if (colonPos < 0)
				{
					logError("Badly formed ArcProto (missing colon): " + piece);
					break;
				}
				techName = piece.substring(0, colonPos);
                techId = idManager.newTechId(techName);
				String arcName = piece.substring(colonPos+1);
				commaPos = arcName.indexOf(',');
				if (commaPos >= 0) arcName = arcName.substring(0, commaPos);
				return techId.newArcProtoId(arcName);
			case 'S':		// String
				if (revision >= 1) return piece;
				if (piece.charAt(0) != '"')
				{
					logError("Badly formed string variable (missing open quote): " + piece);
					break;
				}
				StringBuffer sb = new StringBuffer();
				int len = piece.length();
				int objectPos = 0;
				while (objectPos < len)
				{
					objectPos++;
					if (piece.charAt(objectPos) == '"') break;
					if (piece.charAt(objectPos) == '^')
					{
						objectPos++;
						if (objectPos <= len - 2 && piece.charAt(objectPos) == '\\' && piece.charAt(objectPos+1) == 'n')
						{
							sb.append('\n');
							objectPos++;
							continue;
						}
					}
					sb.append(piece.charAt(objectPos));
				}
				return sb.toString();
			case 'T':		// TechId
				techName = piece;
				commaPos = techName.indexOf(',');
				if (commaPos >= 0) techName = techName.substring(0, commaPos);
                return idManager.newTechId(techName);
			case 'V':		// Point2D
				int slashPos = piece.indexOf('/');
				if (slashPos < 0)
				{
					logError("Badly formed Point2D variable (missing slash): " + piece);
					break;
				}
				double x = Double.valueOf(piece.substring(0, slashPos));
				double y = Double.valueOf(piece.substring(slashPos+1));
				return EPoint.fromLambda(x, y);
			case 'Y':		// Byte
				return Byte.valueOf(piece);
		}
		return null;
	}

    private static double readDouble(String s) {
        return s.length() > 0 ? Double.parseDouble(s) : 0;
    }

    private void logError(String message) {
        String s = curReadFile + ", line " + lineReader.getLineNumber() + ", " + message;
        errorLogger.logError(s, -1);
    }

    private void logWarning(String message) {
        String s = curReadFile + ", line " + lineReader.getLineNumber() + ", " + message;
        errorLogger.logWarning(s, null, -1);
    }

    private void logError(String message, CellId cellId) {
        String s = curReadFile + ", line " + lineReader.getLineNumber() + ", " + message;
        errorLogger.logError(s, cellId, -1);
    }
}
