diff options
4 files changed, 734 insertions, 0 deletions
diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/stl/StlDataStore.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/stl/StlDataStore.java new file mode 100644 index 0000000..6c5ce48 --- /dev/null +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/stl/StlDataStore.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.stl; + +import java.util.ArrayList; +import java.util.List; + +import com.ardor3d.math.Vector3; + +public class StlDataStore { + private final List<Vector3> _vertices = new ArrayList<>(); + private final List<Vector3> _normals = new ArrayList<>(); + + public List<Vector3> getVertices() { + return _vertices; + } + + public List<Vector3> getNormals() { + return _normals; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/stl/StlGeometryStore.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/stl/StlGeometryStore.java new file mode 100644 index 0000000..0b33e9d --- /dev/null +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/stl/StlGeometryStore.java @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.stl; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.util.geom.BufferUtils; + +public class StlGeometryStore { + + private final StlDataStore _dataStore; + + private final Node _root; + + private String _currentObjectName; + + public StlGeometryStore() { + super(); + _dataStore = new StlDataStore(); + _root = new Node(); + } + + public StlDataStore getDataStore() { + return _dataStore; + } + + public Node getScene() { + return _root; + } + + void setCurrentObjectName(final String name) { + commitObjects(); + _currentObjectName = name; + } + + void commitObjects() { + if (!_dataStore.getNormals().isEmpty()) { + final String name; + if (_currentObjectName == null) { + name = "stl_mesh"; + } else { + name = _currentObjectName; + } + + // mesh object to return + final Mesh mesh = new Mesh(name); + final MeshData meshData = mesh.getMeshData(); + + // allocate buffers + final int numberTriangles = _dataStore.getNormals().size(); + final int vertexBufferSize = 3 * numberTriangles; + final FloatBuffer vertexBuffer = BufferUtils.createVector3Buffer(vertexBufferSize); + final FloatBuffer normalBuffer = BufferUtils.createVector3Buffer(vertexBufferSize); + final FloatBuffer colourBuffer = BufferUtils.createColorBuffer(vertexBufferSize); + + // fill buffers + int vertexCount = 0; + int normalCount = 0; + int colourCount = 0; + Vector3 v0; + Vector3 v1; + Vector3 v2; + Vector3 n; + final ReadOnlyColorRGBA defaultColour = ColorRGBA.WHITE; + for (int i = 0; i < numberTriangles; i++) { + // triangle properties + v0 = _dataStore.getVertices().get(3 * i + 0); + v1 = _dataStore.getVertices().get(3 * i + 1); + v2 = _dataStore.getVertices().get(3 * i + 2); + n = _dataStore.getNormals().get(i); + // vertices + BufferUtils.setInBuffer(v0, vertexBuffer, vertexCount++); + BufferUtils.setInBuffer(v1, vertexBuffer, vertexCount++); + BufferUtils.setInBuffer(v2, vertexBuffer, vertexCount++); + // normals - 1 foreach vertex + BufferUtils.setInBuffer(n, normalBuffer, normalCount++); + BufferUtils.setInBuffer(n, normalBuffer, normalCount++); + BufferUtils.setInBuffer(n, normalBuffer, normalCount++); + // colours - 1 foreach vertex + BufferUtils.setInBuffer(defaultColour, colourBuffer, colourCount++); + BufferUtils.setInBuffer(defaultColour, colourBuffer, colourCount++); + BufferUtils.setInBuffer(defaultColour, colourBuffer, colourCount++); + } + + meshData.setVertexBuffer(vertexBuffer); + meshData.setNormalBuffer(normalBuffer); + meshData.setColorBuffer(colourBuffer); + + // indices buffer + final int[] indices = new int[vertexBufferSize]; + for (int i = 0; i < vertexBufferSize; i++) { + indices[i] = i; + } + final IntBuffer iBuffer = BufferUtils.createIntBuffer(indices.length); + iBuffer.put(indices); + meshData.setIndexBuffer(iBuffer); + + _root.attachChild(mesh); + } + } + + void cleanup() { + _currentObjectName = null; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/stl/StlImporter.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/stl/StlImporter.java new file mode 100644 index 0000000..dbe2cdb --- /dev/null +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/stl/StlImporter.java @@ -0,0 +1,556 @@ +/** + * Copyright (c) 2008-2016 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.stl; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StreamTokenizer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.extension.model.util.FileHelper; +import com.ardor3d.math.Vector3; +import com.ardor3d.util.resource.ResourceLocator; +import com.ardor3d.util.resource.ResourceLocatorTool; +import com.ardor3d.util.resource.ResourceSource; + +/** + * Reads an STL (STereoLithography) file and builds an Ardor3D Mesh. The STL format consists entirely of triangles and + * as a result is a simple format to handle. Also, it is widely supported by the CAD/CAM community. + * + * This class supports both ASCII and Binary formats and files residing either locally or on a network. + * + * Refer to <a href="http://en.wikipedia.org/wiki/STL_(file_format)" target="_blank>Wikipedia</a>. Several STL models + * can be downloaded freely from <a href="http://grabcad.com" target="_blank">GrabCAD</a>. + * + * @author gmseed + * @see StlFileParser + */ +public class StlImporter { + + /** + * Extends StreamTokenizer for parsing STL files. The STL format for Ascii files is as follows: + * + * <pre> + * solid name + * ... + * facet normal ni nj nk + * outer loop + * vertex v1x v1y v1z + * vertex v2x v2y v2z + * vertex v3x v3y v3z + * endloop + * endfacet + * ... + * endsolid name + * </pre> + * + * @author gmseed + */ + public static class StlFileParser extends StreamTokenizer { + + /** + * Constructor. + * + * @param reader + * The Reader. + */ + public StlFileParser(final Reader reader) { + super(reader); + resetSyntax(); + eolIsSignificant(true); + lowerCaseMode(true); + + // all printable ascii characters + wordChars('!', '~'); + + whitespaceChars(' ', ' '); + whitespaceChars('\n', '\n'); + whitespaceChars('\r', '\r'); + whitespaceChars('\t', '\t'); + } + + /** + * Gets a number from the stream. Need to extract numbers since they may be in scientific notation. The number + * is returned in nval. + * + * @return Logical-true if successful, else logical-false. + */ + protected boolean getNumber() { + try { + nextToken(); + if (ttype != StreamTokenizer.TT_WORD) { + return false; + } + nval = Double.valueOf(sval).doubleValue(); + } catch (final IOException e) { + System.err.println(e.getMessage()); + return false; + } catch (final NumberFormatException e) { + System.err.println(e.getMessage()); + return false; + } + return true; + } + + } + + // logger + private static final Logger LOGGER = Logger.getLogger(StlImporter.class.getName()); + + private ResourceLocator _modelLocator; + + /** + * Constructor. + */ + public StlImporter() { + super(); + } + + /** + * Reads a STL file from the given resource + * + * @param resource + * the name of the resource to find. + * @return a StlGeometryStore data object containing the scene and other useful elements. + */ + public StlGeometryStore load(final String resource) { + return load(resource, new FileHelper()); + } + + /** + * Reads a STL file from the given resource + * + * @param resource + * the name of the resource to find. + * @param fileHelper + * the file helper used to determine whether the resource is an Ascii file or a binary file + * + * @return a StlGeometryStore data object containing the scene and other useful elements. + */ + public StlGeometryStore load(final String resource, final FileHelper fileHelper) { + final ResourceSource source; + if (_modelLocator == null) { + source = ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_MODEL, resource); + } else { + source = _modelLocator.locateResource(resource); + } + + if (source == null) { + throw new Error("Unable to locate '" + resource + "'"); + } + + return load(source, fileHelper); + } + + /** + * Reads a STL file from the given resource + * + * @param resource + * the resource to find. + * @return a StlGeometryStore data object containing the scene and other useful elements. + */ + public StlGeometryStore load(final ResourceSource resource) { + return load(resource, new FileHelper()); + } + + /** + * Reads a STL file from the given resource + * + * @param resource + * the resource to find. + * @param fileHelper + * the file helper used to determine whether the resource is an Ascii file or a binary file + * + * @return a StlGeometryStore data object containing the scene and other useful elements. + */ + public StlGeometryStore load(final ResourceSource resource, final FileHelper fileHelper) { + final boolean isAscii = fileHelper.isFilePureAscii(resource); + final StlGeometryStore store = new StlGeometryStore(); + if (isAscii) { // Ascii file + try (final BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream()))) { + final StlFileParser parser = new StlFileParser(reader); + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + // read "solid" + if (!parser.sval.equals("solid")) { + StlImporter.LOGGER.log(Level.SEVERE, "Ascii file but no solid keyword"); + } + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + // read object name if any + if (parser.ttype != StreamTokenizer.TT_WORD) { + StlImporter.LOGGER.log(Level.WARNING, + "Format Warning: expecting the object name on line " + parser.lineno()); + } else { + final String objectName = parser.sval; + store.setCurrentObjectName(objectName); + // Reads the EOL for verifying that the file has a correct format + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, + "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + if (parser.ttype != StreamTokenizer.TT_EOL) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting End Of Line on line " + parser.lineno()); + } + } + // read the rest of the file + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + // read all the facets + while (parser.ttype != StreamTokenizer.TT_EOF && !parser.sval.equals("endsolid")) { + // Reads "facet" + if (!(parser.ttype == StreamTokenizer.TT_WORD && parser.sval.equals("facet"))) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error:expecting 'facet' on line " + parser.lineno()); + } else { + try { + parser.nextToken(); + // Reads a normal + if (!(parser.ttype == StreamTokenizer.TT_WORD && parser.sval.equals("normal"))) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error:expecting 'normal' on line " + parser.lineno()); + } else { + if (parser.getNumber()) { + final Vector3 normal = new Vector3(); + normal.setX(parser.nval); + + if (parser.getNumber()) { + normal.setY(parser.nval); + + if (parser.getNumber()) { + normal.setZ(parser.nval); + + store.getDataStore().getNormals().add(normal); + // Reads the EOL for verifying that the file has a correct format + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, + "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + if (parser.ttype != StreamTokenizer.TT_EOL) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting End Of Line on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting normal z-component on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting normal y-component on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting normal x-component on line " + parser.lineno()); + } + } + + parser.nextToken(); + // Reads "outer loop" then EOL + if (!(parser.ttype == StreamTokenizer.TT_WORD && parser.sval.equals("outer"))) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting 'outer' on line " + parser.lineno()); + } else { + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, + "IO error on line " + parser.lineno() + ": " + e.getMessage()); + } + if (!(parser.ttype == StreamTokenizer.TT_WORD && parser.sval.equals("loop"))) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error:expecting 'loop' on line " + parser.lineno()); + } else { + // Reads the EOL for verifying that the file has a correct format + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, + "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + if (parser.ttype != StreamTokenizer.TT_EOL) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting End Of Line on line " + parser.lineno()); + } + } + } + + parser.nextToken(); + // Reads the first vertex + if (!(parser.ttype == StreamTokenizer.TT_WORD && parser.sval.equals("vertex"))) { + System.err.println("Format Error:expecting 'vertex' on line " + parser.lineno()); + } else { + if (parser.getNumber()) { + final Vector3 vertex = new Vector3(); + vertex.setX(parser.nval); + + if (parser.getNumber()) { + vertex.setY(parser.nval); + + if (parser.getNumber()) { + vertex.setZ(parser.nval); + + store.getDataStore().getVertices().add(vertex); + // Reads the EOL for verifying that the file has a correct format + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, + "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + if (parser.ttype != StreamTokenizer.TT_EOL) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting End Of Line on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting vertex z-component on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting vertex y-component on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting vertex x-component on line " + parser.lineno()); + } + } + + parser.nextToken(); + // Reads the second vertex + if (!(parser.ttype == StreamTokenizer.TT_WORD && parser.sval.equals("vertex"))) { + System.err.println("Format Error:expecting 'vertex' on line " + parser.lineno()); + } else { + if (parser.getNumber()) { + final Vector3 vertex = new Vector3(); + vertex.setX(parser.nval); + + if (parser.getNumber()) { + vertex.setY(parser.nval); + + if (parser.getNumber()) { + vertex.setZ(parser.nval); + + store.getDataStore().getVertices().add(vertex); + // Reads the EOL for verifying that the file has a correct format + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, + "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + if (parser.ttype != StreamTokenizer.TT_EOL) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting End Of Line on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting vertex z-component on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting vertex y-component on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting vertex x-component on line " + parser.lineno()); + } + } + + parser.nextToken(); + // Reads the third vertex + if (!(parser.ttype == StreamTokenizer.TT_WORD && parser.sval.equals("vertex"))) { + System.err.println("Format Error:expecting 'vertex' on line " + parser.lineno()); + } else { + if (parser.getNumber()) { + final Vector3 vertex = new Vector3(); + vertex.setX(parser.nval); + + if (parser.getNumber()) { + vertex.setY(parser.nval); + + if (parser.getNumber()) { + vertex.setZ(parser.nval); + + store.getDataStore().getVertices().add(vertex); + // Reads the EOL for verifying that the file has a correct format + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, + "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + if (parser.ttype != StreamTokenizer.TT_EOL) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting End Of Line on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting vertex z-component on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting vertex y-component on line " + + parser.lineno()); + } + } else { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting vertex x-component on line " + parser.lineno()); + } + } + + parser.nextToken(); + // Reads "endloop" then EOL + if (!(parser.ttype == StreamTokenizer.TT_WORD && parser.sval.equals("endloop"))) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting 'endloop' on line " + parser.lineno()); + } else { + // Reads the EOL for verifying that the file has a correct format + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, + "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + if (parser.ttype != StreamTokenizer.TT_EOL) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting End Of Line on line " + parser.lineno()); + } + } + + parser.nextToken(); + // Reads "endfacet" then EOL + if (!(parser.ttype == StreamTokenizer.TT_WORD && parser.sval.equals("endfacet"))) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error:expecting 'endfacet' on line " + parser.lineno()); + } else { + // Reads the EOL for verifying that the file has a correct format + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, + "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + if (parser.ttype != StreamTokenizer.TT_EOL) { + StlImporter.LOGGER.log(Level.SEVERE, + "Format Error: expecting End Of Line on line " + parser.lineno()); + } + } + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, + "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + } + try { + parser.nextToken(); + } catch (final IOException e) { + StlImporter.LOGGER.log(Level.SEVERE, + "IO Error on line " + parser.lineno() + ": " + e.getMessage()); + } + } + } catch (final Throwable t) { + throw new Error("Unable to load stl resource from URL: " + resource, t); + } + } else { // Binary file + try (final InputStream data = resource.openStream()) { + ByteBuffer dataBuffer; // To read in the correct endianness + final byte[] info = new byte[80]; // Header data + final byte[] numberFaces = new byte[4]; // the number of faces + byte[] faceData; // face data + int numberTriangles; // First info (after the header) on the file + + // the first 80 bytes aren't important (except if you want to support non standard colors) + if (80 != data.read(info)) { + throw new IOException("Format Error: 80 bytes expected"); + } else { + // read number of faces, setting the correct order + data.read(numberFaces); + dataBuffer = ByteBuffer.wrap(numberFaces); + dataBuffer.order(ByteOrder.nativeOrder()); + + // allocate buffer for face data, with each face requiring 50 bytes + numberTriangles = dataBuffer.getInt(); + faceData = new byte[50 * numberTriangles]; + + // read face data + data.read(faceData); + dataBuffer = ByteBuffer.wrap(faceData); + dataBuffer.order(ByteOrder.nativeOrder()); + + // read each facet noting that after each fact there are 2 bytes without information + // no need to skip for last iteration + for (int index = 0; index < numberTriangles; index++) { + try { + // Reads a facet from a binary file. + // normal + store.getDataStore().getNormals().add( + new Vector3(dataBuffer.getFloat(), dataBuffer.getFloat(), dataBuffer.getFloat())); + // 3 vertices + store.getDataStore().getVertices().add( + new Vector3(dataBuffer.getFloat(), dataBuffer.getFloat(), dataBuffer.getFloat())); + store.getDataStore().getVertices().add( + new Vector3(dataBuffer.getFloat(), dataBuffer.getFloat(), dataBuffer.getFloat())); + store.getDataStore().getVertices().add( + new Vector3(dataBuffer.getFloat(), dataBuffer.getFloat(), dataBuffer.getFloat())); + if (index != numberTriangles - 1) { + dataBuffer.get(); + dataBuffer.get(); + } + } catch (final Throwable t) { + throw new Exception("Format Error: iteration number " + index, t); + } + } + } + } catch (final Throwable t) { + throw new Error("Unable to load stl resource from URL: " + resource, t); + } + } + + store.commitObjects(); + store.cleanup(); + return store; + + } + + public void setModelLocator(final ResourceLocator locator) { + _modelLocator = locator; + } +} diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/FileHelper.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/FileHelper.java index 5f8ae2b..8a9c5cf 100644 --- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/FileHelper.java +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/util/FileHelper.java @@ -13,6 +13,7 @@ package com.ardor3d.extension.model.util; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.FileInputStream; +import java.io.InputStream; import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.nio.CharBuffer; @@ -20,6 +21,8 @@ import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import java.nio.charset.StandardCharsets; +import com.ardor3d.util.resource.ResourceSource; + public class FileHelper { /** @@ -71,4 +74,30 @@ public class FileHelper { } return true; } + + /** + * Tests whether or not the file with the specified filename is pure ASCII. The method used is to read the file a + * line at a time and test if each line is ASCII. + * + * @param resource + * the name of the resource to find. + * @return Logical-true if pure ASCII, else logical-false. + */ + public boolean isFilePureAscii(final ResourceSource resource) { + try (final InputStream inputStream = resource.openStream(); + final DataInputStream in = new DataInputStream(inputStream); + final BufferedReader br = new BufferedReader(new InputStreamReader(in))) { + String strLine; + // read file a line at a time + while ((strLine = br.readLine()) != null) { + final boolean isAscii = isStringPureAscii(strLine); + if (!isAscii) { + return false; + } + } + } catch (final Exception e) { + return false; + } + return true; + } } |