/*
* Project Info: http://jcae.sourceforge.net
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This program 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* (C) Copyright 2009, by EADS France
*/
package org.jcae.mesh.xmldata;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.jcae.mesh.amibe.metrics.Location;
import org.xml.sax.SAXException;
/**
* Helper class to write amibe files.
* The format description can be found at
* http://jcae.sourceforge.net/amibe.html#Amibe+file+format
* @author Jerome Robert
*/
public abstract class AmibeWriter {
private final static Logger LOGGER = Logger.getLogger(AmibeWriter.class.getName());
private DataOutputStream bGroupChan;
public static class Dim1 extends AmibeWriter {
public Dim1(String name) throws IOException {
init(name, true);
}
public void addNode(double x) throws IOException
{
nodeChan.writeDouble(x);
numberOfNodes ++;
}
@Override
protected int dim() {
return 1;
}
}
public static class Dim2 extends AmibeWriter {
private final String binDirectory, xmlFile;
public Dim2(String name, int index) throws IOException {
binDirectory = "jcae2d."+ index +".files";
xmlFile = "jcae2d."+index;
init(name, true);
}
public void addNode(double x, double y) throws IOException
{
nodeChan.writeDouble(x);
nodeChan.writeDouble(y);
numberOfNodes ++;
}
@Override
protected int dim() {
return 2;
}
@Override
protected String binDirectory() {
return binDirectory;
}
@Override
protected String xmlFile() {
return xmlFile;
}
}
public static class Dim3 extends AmibeWriter {
public Dim3(String name, boolean normal, boolean hasRef) throws IOException {
init(name, hasRef);
if(normal)
{
File dir3d = new File(name, binDirectory());
File f = new File(dir3d, JCAEXMLData.normals3dFilename);
normalChan = createDOS(f);
}
}
public Dim3(String name) throws IOException {
this(name, false);
}
public Dim3(String name, boolean normal) throws IOException {
this(name, normal, false);
}
public void addNode(double x, double y, double z) throws IOException
{
nodeChan.writeDouble(x);
nodeChan.writeDouble(y);
nodeChan.writeDouble(z);
numberOfNodes ++;
}
@Override
protected int dim() {
return 3;
}
public void addNormal(double x, double y, double z) throws IOException
{
normalChan.writeDouble(x);
normalChan.writeDouble(y);
normalChan.writeDouble(z);
}
}
/**
* Contains informations about groups, which will be written to the
* XML file
*/
private static class Group
{
String name;
long offset;
int nbElement;
long bOffset;
int bNbElement;
}
private static class NIOutputStream extends OutputStream
{
private ByteBuffer bb = ByteBuffer.allocate(1);
private ByteBuffer bb2;
private final FileChannel channel;
public NIOutputStream(FileChannel c) {
this.channel = c;
}
@Override
public void write(int b) throws IOException {
bb.put(0, (byte)b);
bb.rewind();
channel.write(bb);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if(bb2 == null || bb2.capacity() != len)
bb2 = ByteBuffer.allocate(len);
else
bb2.rewind();
bb2.put(b, off, len);
bb2.rewind();
channel.write(bb2);
}
@Override
public void close() throws IOException {
channel.close();
}
}
private static DataOutputStream createDOS(File f) throws FileNotFoundException
{
FileOutputStream fos = new FileOutputStream(f);
return new DataOutputStream(new BufferedOutputStream(
new NIOutputStream(fos.getChannel()), 1024*64));
}
protected DataOutputStream nodeChan, triaChan, groupChan, refChan,
beamChan, normalChan, nodeGroupChan;
private XMLWriter xmlWriter;
protected int numberOfNodes, numberOfTriangles, numberOfRef, numberOfBeams;
private List<Group> groups = new ArrayList<Group>();
private List<Group> nodeGroups = new ArrayList<Group>();
private long groupOffset, bGroupOffset, nodeGroupOffset;
private Group currentGroup;
private Group currentNodeGroup;
private boolean checkNoGroup;
private String shape;
private boolean shapeWritten;
private int subShape;
private boolean haveSubShape;
/** Set the subShape */
public void setSubShape(int i)
{
haveSubShape = true;
subShape = i;
}
public void setShape(String shape)
{
if(shapeWritten)
//only one shape by file
throw new IllegalStateException();
this.shape = shape;
}
public void addNodeRef(int n) throws IOException
{
if (null == refChan)
return;
refChan.writeInt(n);
numberOfRef++;
}
public void addNode(Location location) throws IOException
{
assert location.dim() == dim();
for(int i = 0; i<location.dim(); i++)
nodeChan.writeDouble(location.get(i));
numberOfNodes ++;
}
public void addNode(double[] coords) throws IOException
{
assert coords.length == dim();
for(int i = 0; i<coords.length; i++)
nodeChan.writeDouble(coords[i]);
numberOfNodes ++;
}
/** Add a triangle to the current submesh */
public void addTriangle(int i, int j, int k) throws IOException
{
triaChan.writeInt(i);
triaChan.writeInt(j);
triaChan.writeInt(k);
numberOfTriangles ++;
}
/** Add a triangle to the current submesh */
public void addTriangle(int[] indices) throws IOException
{
triaChan.writeInt(indices[0]);
triaChan.writeInt(indices[1]);
triaChan.writeInt(indices[2]);
numberOfTriangles ++;
}
public void addBeam(int i, int j) throws IOException
{
beamChan.writeInt(i);
beamChan.writeInt(j);
numberOfBeams ++;
}
/**
* create a new sub mesh and make it current.
* There is no need to call this method to create the first submesh.
* submesh are currently only used when exporting the 1D mesh during the
* meshing process (see http://jcae.sourceforge.net/amibe.html#Algorithm)
*/
public void nextSubMesh() throws IOException
{
writeSubMesh();
nodesOffset = numberOfNodes;
triaOffset = numberOfTriangles;
beamsOffset = numberOfBeams;
numberOfNodes = 0;
numberOfTriangles = 0;
numberOfRef = 0;
numberOfBeams = 0;
groups.clear();
haveSubShape = false;
}
/** Create a new group and make it the current group */
public void nextGroup(String name)
{
if(name == null)
throw new NullPointerException();
Group g=new Group();
g.name = name;
g.offset= groupOffset;
g.bOffset= bGroupOffset;
groups.add(g);
currentGroup = g;
}
/**
* Add a triangle to the current group
* nextGroup must have been called before calling this method
* @param id the ID of the triangle to add
*/
public void addTriaToGroup(int id) throws IOException
{
groupChan.writeInt(id);
groupOffset ++;
currentGroup.nbElement ++;
}
/**
* Add a beam to the current group
* nextGroup must have been called before calling this method
* @param id the ID of the beam to add
*/
public void addBeamToGroup(int id) throws IOException
{
bGroupChan.writeInt(id);
bGroupOffset ++;
currentGroup.bNbElement ++;
}
public void finish() throws IOException
{
try {
writeSubMesh();
xmlWriter.out.writeEndElement();
xmlWriter.out.writeEndElement();
xmlWriter.close();
nodeChan.close();
triaChan.close();
groupChan.close();
nodeGroupChan.close();
beamChan.close();
bGroupChan.close();
if(refChan != null)
refChan.close();
} catch (SAXException ex) {
LOGGER.log(Level.SEVERE, null, ex);
} catch (XMLStreamException ex) {
LOGGER.log(Level.SEVERE, null, ex);
}
}
private String nodeFName, triaFName, refFName, beamsFName;
private int nodesOffset, beamsOffset, triaOffset;
private DoubleFileReader nodesReader;
private File fnode;
protected final void init(String path, boolean writeReferences) throws IOException
{
try {
new File(path).mkdirs();
nodeFName = "nodes" + dim() + "d.bin";
triaFName = "triangles" + dim() + "d.bin";
beamsFName = "beams" + dim() + "d.bin";
File dir3d = new File(path, binDirectory());
dir3d.mkdirs();
fnode = new File(dir3d, nodeFName );
File ftria = new File(dir3d, triaFName );
File fgrp = new File(dir3d, "groups.bin");
File fbeams = new File(dir3d, beamsFName);
File bgroups = new File(dir3d, "bgroups.bin");
if(writeReferences)
{
refFName = "nodes1dref.bin";
File ref = new File(dir3d, refFName);
refChan = createDOS(ref);
}
nodeChan = createDOS(fnode);
triaChan = createDOS(ftria);
groupChan = createDOS(fgrp);
bGroupChan = createDOS(bgroups);
nodeGroupChan = createDOS(new File(dir3d, "nodeGroups.bin"));
beamChan = createDOS(fbeams);
xmlWriter = new XMLWriter(new File(path, xmlFile()).getPath(),
getClass().getResource("jcae.xsd"));
xmlWriter.out.writeStartElement("jcae");
xmlWriter.out.writeStartElement("mesh");
} catch (XMLStreamException ex) {
LOGGER.log(Level.SEVERE, null, ex);
}
}
protected abstract int dim();
protected String binDirectory()
{
return "jcae" + dim() + "d.files";
}
protected String xmlFile()
{
return "jcae" + dim() + "d";
}
public void getNode(int i, double[] nc) throws IOException {
if(nodesReader == null)
{
nodesReader = new DoubleFileReaderByDirectBuffer(fnode);
}
nodesReader.get(i * dim(), nc);
}
private void writeNumber(int i) throws XMLStreamException
{
XMLStreamWriter o = xmlWriter.out;
o.writeStartElement("number");
o.writeCharacters(Integer.toString(i));
o.writeEndElement();
}
private void writeFile(String format, String location, long offset) throws XMLStreamException
{
XMLStreamWriter o = xmlWriter.out;
o.writeStartElement("file");
o.writeAttribute("format", format);
o.writeAttribute("location", location);
if(offset != 0)
o.writeAttribute("offset", Long.toString(offset));
o.writeEndElement();
}
private void writeSubMesh() throws IOException
{
try
{
XMLStreamWriter o = xmlWriter.out;
if(shape != null && !shapeWritten)
{
o.writeStartElement("shape");
writeFile("brep", shape, 0);
o.writeEndElement();
shapeWritten = true;
}
String dir = binDirectory()+"/";
o.writeStartElement("submesh");
if(haveSubShape)
{
o.writeStartElement("subshape");
o.writeCharacters(Integer.toString(subShape));
o.writeEndElement();
}
o.writeStartElement("nodes");
writeNumber(numberOfNodes);
writeFile("doublestream", dir + nodeFName, nodesOffset);
if(refChan != null)
{
o.writeStartElement("references");
writeNumber(numberOfRef);
writeFile("integerstream", dir + refFName, 0);
o.writeEndElement();
}
o.writeEndElement(); //nodes
if(numberOfBeams > 0)
{
o.writeStartElement("beams");
writeNumber(numberOfBeams);
writeFile("integerstream", dir + beamsFName, beamsOffset);
o.writeEndElement();
}
if(numberOfTriangles > 0)
{
o.writeStartElement("triangles");
writeNumber(numberOfTriangles);
writeFile("integerstream", dir + triaFName, triaOffset);
if(null != normalChan)
{
o.writeStartElement("normals");
writeFile("doublestream", dir + JCAEXMLData.normals3dFilename, 0);
o.writeEndElement();
}
o.writeEndElement(); //triangles
}
writeGroups();
writeNodeGroups();
o.writeEndElement(); //submesh
} catch (XMLStreamException ex) {
Logger.getLogger(AmibeWriter.class.getName()).log(Level.SEVERE, null,
ex);
}
}
private void writeGroups() throws XMLStreamException, IOException
{
if (checkNoGroup) {
checkNoGroup();
}
if(!groups.isEmpty())
{
XMLStreamWriter o = xmlWriter.out;
o.writeStartElement("groups");
int id = 1;
for (Group g : groups) {
o.writeStartElement("group");
o.writeAttribute("id", Integer.toString(id++));
o.writeStartElement("name");
o.writeCharacters(g.name);
o.writeEndElement();
if(g.nbElement > 0)
{
writeNumber(g.nbElement);
writeFile("integerstream", binDirectory() + "/groups.bin", g.offset);
}
if(g.bNbElement > 0)
{
o.writeStartElement("beams");
writeNumber(g.bNbElement);
writeFile("integerstream", binDirectory() + "/bgroups.bin", g.bOffset);
o.writeEndElement();
}
o.writeEndElement();
}
o.writeEndElement(); //groups
}
}
/**
* Enable the fix group property.
* When the fix group property is enabled and no group has been added to
* the mesh, a default group containing all elements is created.
*/
public void setFixNoGroup(boolean b)
{
checkNoGroup = b;
}
/**
* If do not contains any groups, create one with all
* elements
*/
private void checkNoGroup() throws IOException
{
int trianglesInGroup = 0;
int edgesInGroup = 0;
for(Group g: groups)
{
trianglesInGroup += g.nbElement;
edgesInGroup += g.bNbElement;
}
if(trianglesInGroup == 0 && numberOfTriangles > 0)
{
Group g=new Group();
g.name="C_EXT";
g.nbElement=numberOfTriangles;
g.offset=0;
groups.add(g);
for(int i=0; i<numberOfTriangles; i++)
groupChan.writeInt(i);
}
if(edgesInGroup == 0 && numberOfBeams > 0)
{
Group g=new Group();
g.name="CF_EXT";
g.bNbElement=numberOfBeams;
g.bOffset=0;
groups.add(g);
for(int i=0; i<numberOfBeams; i++)
bGroupChan.writeInt(i);
}
}
private void writeNodeGroups() throws XMLStreamException, IOException
{
if(!nodeGroups.isEmpty())
{
XMLStreamWriter o = xmlWriter.out;
o.writeStartElement("nodeGroups");
for (Group g : nodeGroups) {
o.writeStartElement("group");
o.writeStartElement("name");
o.writeCharacters(g.name);
o.writeEndElement();
if(g.nbElement > 0)
{
writeNumber(g.nbElement);
writeFile("integerstream", binDirectory() + "/nodeGroups.bin", g.offset);
}
o.writeEndElement();
}
o.writeEndElement(); //groups
}
}
/** Create a new group and make it the current node group */
public void nextNodeGroup(String name)
{
if(name == null)
throw new NullPointerException();
Group g=new Group();
g.name = name;
g.offset= nodeGroupOffset;
nodeGroups.add(g);
currentNodeGroup = g;
}
/**
* Add a node to the current node group
* nextNodeGroup must have been called before calling this method
* @param id the ID of the node to add
*/
public void addNodeToGroup(int nodeID) throws IOException
{
nodeGroupChan.writeInt(nodeID);
nodeGroupOffset ++;
currentNodeGroup.nbElement ++;
}
}