Skip to content

Commit

Permalink
Store codecs in ThreadLocal
Browse files Browse the repository at this point in the history
- added Codec class to maintain codecs in thread local variable
- all threads attach Codec instance on first Codec use
- thread local variable detached from thread calling IModel.close()
  - or when model autoclosed after try-with-resources involving thread
  - or when thread explicitly calls static IModel.detach()

Signed-off-by: jrte <jrte.project@gmail.com>
  • Loading branch information
jrte committed Oct 8, 2023
1 parent 857b3f8 commit 87f6054
Show file tree
Hide file tree
Showing 26 changed files with 473 additions and 452 deletions.
2 changes: 1 addition & 1 deletion build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@
<target name="javadoc" unless="javadoc.uptodate" depends="check-javadoc">
<delete dir="javadoc" includes="**/*"/>
<javadoc executable="${jdk}/bin/javadoc" access="protected" overview="overview.html" additionalparam="-quiet"
sourcepath="src" destdir="javadoc" verbose="false" use="true" failonerror="false" failonwarning="false"
sourcepath="src" destdir="javadoc" verbose="false" use="true" failonerror="true" failonwarning="false"
doctitle="Ribose" packagenames="com.characterforming.ribose.base,com.characterforming.ribose"
encoding="UTF-8" charset="UTF-8" docencoding="UTF-8">
<group title="Interfaces and Supporting Classes" packages="com.characterforming.ribose*"/>
Expand Down
5 changes: 3 additions & 2 deletions src/com/characterforming/jrte/engine/Automaton.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.CharacterCodingException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.logging.Level;
Expand Down Expand Up @@ -79,7 +80,7 @@ boolean assemble(File inrAutomataDirectory) {
if (commit) {
this.compiler.saveTransducer();
}
} catch (ModelException | ParseException e) {
} catch (ModelException | ParseException | CharacterCodingException e) {
String msg = String.format("%1$s: Exception caught assembling compiler model file; %2$s",
filename, e.getMessage());
rtcLogger.log(Level.SEVERE, msg, e);
Expand All @@ -91,7 +92,7 @@ boolean assemble(File inrAutomataDirectory) {
return commit;
}

private boolean parse(byte[] dfa) throws ParseException {
private boolean parse(byte[] dfa) throws ParseException, CharacterCodingException {
this.dfain.clear(dfa);
this.line = 0;

Expand Down
27 changes: 0 additions & 27 deletions src/com/characterforming/jrte/engine/Base.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@

package com.characterforming.jrte.engine;

import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
Expand Down Expand Up @@ -57,7 +53,6 @@ private Base() {
private static final int FILE_LOGGER_LIMIT = 1024 * 1024;
private static final int INPUT_BUFFER_SIZE = Integer.parseInt(System.getProperty("ribose.inbuffer.size", "65536"));
private static final int OUTPUT_BUFFER_SIZE = Integer.parseInt(System.getProperty("ribose.outbuffer.size", "8196"));
private static final Charset runtimeCharset = Charset.forName(System.getProperty("ribose.runtime.charset", "UTF-8"));
private static final Logger rtcLogger = Logger.getLogger("ribose-compile");
private static final Logger rteLogger = Logger.getLogger("ribose-runtime");

Expand Down Expand Up @@ -98,28 +93,6 @@ public static void endLogging() {
Base.endLogger(Base.rteLogger);
}

/**
* Instantiate a new {@code CharsetDecoder}. All textual data in ribose models
* are represented in encoded form (eg, UTF-8 byte arrays).
*
* @return a new CharsetDecoder insstance
*/
public static CharsetDecoder newCharsetDecoder() {
return Base.runtimeCharset.newDecoder()
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.onMalformedInput(CodingErrorAction.REPLACE);
}

/**
* Instantiate a new {@code CharsetEncoder}. All textual data in ribose models
* are represented in encoded form (eg, UTF-8 byte arrays).
*
* @return a new CharsetEncoder instance
*/
public static CharsetEncoder newCharsetEncoder() {
return Base.runtimeCharset.newEncoder();
}

/**
* Get the size (in bytes) to use for input buffers.
*
Expand Down
5 changes: 4 additions & 1 deletion src/com/characterforming/jrte/engine/BaseFieldEffector.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

package com.characterforming.jrte.engine;

import java.nio.charset.CharacterCodingException;

import com.characterforming.ribose.IToken;
import com.characterforming.ribose.base.BaseParameterizedEffector;
import com.characterforming.ribose.base.TargetBindingException;
Expand All @@ -37,8 +39,9 @@ abstract class BaseFieldEffector extends BaseParameterizedEffector<Transductor,
*
* @param transductor The transductor target that binds the effector
* @param name the field name
* @throws CharacterCodingException
*/
protected BaseFieldEffector(final Transductor transductor, final String name) {
protected BaseFieldEffector(final Transductor transductor, final String name) throws CharacterCodingException {
super(transductor, name);
}

Expand Down
16 changes: 10 additions & 6 deletions src/com/characterforming/jrte/engine/BaseInputOutputEffector.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

package com.characterforming.jrte.engine;

import java.nio.charset.CharacterCodingException;

import com.characterforming.ribose.IToken;
import com.characterforming.ribose.base.BaseParameterizedEffector;
import com.characterforming.ribose.base.EffectorException;
Expand All @@ -34,8 +36,9 @@ abstract class BaseInputOutputEffector extends BaseParameterizedEffector<Transdu
*
* @param transductor The transductor target that binds the effector
* @param name the field name
* @throws CharacterCodingException
*/
protected BaseInputOutputEffector(Transductor transductor, String name) {
protected BaseInputOutputEffector(Transductor transductor, String name) throws CharacterCodingException {
super(transductor, name);
}

Expand All @@ -52,15 +55,16 @@ public IToken[][] allocateParameters(int parameterCount) {
@Override // @see com.characterforming.ribose.IParameterizedEffector#compileParameter(IToken[])
public IToken[] compileParameter(final IToken[] parameterList) throws TargetBindingException {
if (parameterList.length < 1) {
throw new TargetBindingException(String.format("%1$s.%2$s[]: effector requires at least one parameter",
throw new TargetBindingException(String.format(
"%1$s.%2$s[]: effector requires at least one parameter",
super.target.getName(), super.getName()));
}
for (IToken token : parameterList) {
if (token.getType() != IToken.Type.LITERAL && token.getType() != IToken.Type.FIELD) {
throw new TargetBindingException(
String.format("%1$s.%2$s[]: literal or field name expected, found '%3$s'",
super.target.getName(), super.getName().toString(super.decoder()),
token.getLiteral().toString(super.decoder())));
throw new TargetBindingException(String.format(
"%1$s.%2$s[]: literal or field name expected, found '%3$s'",
super.target.getName(), super.getName().asString(),
token.asString()));
}
}
return parameterList;
Expand Down
105 changes: 105 additions & 0 deletions src/com/characterforming/jrte/engine/Codec.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/***
* Ribose is a recursive transduction engine for Java
*
* Copyright (C) 2011,2022 Kim Briggs
*
* 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 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (LICENSE-gpl-3.0). If not, see
* <http://www.gnu.org/licenses/#GPL>.
*/

package com.characterforming.jrte.engine;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;

import com.characterforming.ribose.base.Bytes;

public class Codec {
private static final Charset CHARSET = Charset.forName(System.getProperty("ribose.runtime.charset", "UTF-8"));
private static final ThreadLocal<Codec> LOCAL = ThreadLocal.withInitial(Codec::new);
private CharsetDecoder decoder;
private CharsetEncoder encoder;

private Codec() {
this.decoder = Codec.CHARSET.newDecoder()
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.onMalformedInput(CodingErrorAction.REPLACE)
.replaceWith("~")
.reset();
this.encoder = Codec.CHARSET.newEncoder()
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.onMalformedInput(CodingErrorAction.REPLACE)
.replaceWith("~".getBytes())
.reset();
}

private static Codec set() {
Codec codec = new Codec();
Codec.LOCAL.set(codec);
return codec;
}

private static Codec get() {
Codec codec = Codec.LOCAL.get() ;
return (codec == null) ? Codec.set() : codec;
}

public static void detach() {
Codec.LOCAL.remove();
}

/**
* Decode UTF-8 bytes to a String.
*
* @param bytes the bytes to decode
* @param length the number of bytes to decode
* @return a String containing the decoded text
* @throws CharacterCodingException if decoding fails
*/
public static String decode(final byte[] bytes, final int length) throws CharacterCodingException {
assert 0 <= length && length <= bytes.length;
int size = Math.max(Math.min(length, bytes.length), 0);
return Codec.get().decoder.reset().decode(ByteBuffer.wrap(bytes, 0, size)).toString();
}

/**
* Decode UTF-8 bytes to a String.
*
* @param bytes the bytes to decode
* @return a String containing the decoded text
* @throws CharacterCodingException if decoding fails
*/
public static String decode(final byte[] bytes) throws CharacterCodingException {
return Codec.decode(bytes, bytes.length);
}

/**
* Encode a String.
*
* @param chars the string to encode
* @return the encoded Bytes
* @throws CharacterCodingException if encoding fails
*/
public static Bytes encode(final String chars) throws CharacterCodingException {
ByteBuffer buffer = Codec.get().encoder.reset().encode(CharBuffer.wrap(chars.toCharArray()));
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes, 0, bytes.length);
return new Bytes(bytes);
}
}
Loading

1 comment on commit 87f6054

@jrte
Copy link
Owner Author

@jrte jrte commented on 87f6054 Oct 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes #36

Please sign in to comment.