All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jclarion.clarion.ClarionString Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2010, by Andrew Barnham
 *
 * The contents of this file are subject to
 * GNU Lesser General Public License (LGPL), v.3
 * http://www.gnu.org/licenses/lgpl.txt
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 */
package org.jclarion.clarion;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jclarion.clarion.constants.Match;
import org.jclarion.clarion.lang.Constant;
import org.jclarion.clarion.lang.StringEncoding;
import org.jclarion.clarion.memory.CMem;
import org.jclarion.clarion.primative.AbstractStateFactory;
import org.jclarion.clarion.primative.AbstractStateGetter;
import org.jclarion.clarion.primative.GlobalStateGetter;
import org.jclarion.clarion.primative.ThreadStateGetter;
import org.jclarion.clarion.runtime.format.Formatter;


/**
 * Model a string in clarion
 * 
 * Internal storage of string uses following parameters:
 * 
 *  lState.string           - String
 *  lState.chararray        - chararray
 *  lState.chararray_len    - string len in char array
 *  
 *  lState.chararry is the accurate representation. and lState.string is non null
 *  when requested and chararray has not subsequently mutated
 *  
 *  it is possible for string to be defined by chararray not to be defined
 *  when first mutation is needed - chararray is retrieved from the string
 * 
 * 
 * @author barney
 *
 */
public class ClarionString extends ClarionObject 
{
    public static final int STRING=1;
    public static final int CSTRING=2;
    public static final int PSTRING=3;
    public static final int ASTRING=4;

    private boolean contentConstructed;     // true if object was constructed via content
    private int encoding=STRING;
    private int size=-1;

    public static class State
    {
        private String string;
        private char[] chararray;
        private int    len;

        public State(ClarionString.State base) {
            string=base.string;
            len=base.len;
            if (base.chararray!=null) chararray=base.chararray.clone(); 
        }
        public State() {
        }
    }
    
    private static class StateFactory extends AbstractStateFactory
    {
        @Override
        public State cloneState(State base) {
            return new State(base);
        }

        @Override
        public State createState() {
            return new State();
        }
    }
    private static StateFactory factory=new StateFactory();
    
    private AbstractStateGetter state=new GlobalStateGetter(factory);
    
    public void initThread()
    {
        if (!state.isThreaded()) state=new ThreadStateGetter(factory,state);
        super.initThread();
    }
    
    public ClarionString setThread()
    {
        initThread();
        return this;
    }
    
    @Override
    public Object getLockedObject(Thread t) 
    {
        if (!state.isThreaded()) return this;
        return new ClarionString(this,t);
    }
    
    public ClarionString(ClarionString base,Thread lock)
    {
        super(base,lock);
        this.encoding=base.encoding;
        this.contentConstructed=base.contentConstructed;
        this.size=base.size;
        if (lock!=null) this.state=base.state.getLockedGetter(lock);
    }
    
    
    
    /**
     * Create undefined string
     */
    public ClarionString()
    {
    }

    /**
     * Create empty string of specified size. space padded.
     */
    public ClarionString(int size)
    {
        this.size=size;
        clear();
    }

    /**
     * Create string of given size defaulted to specified content.
     */
    public ClarionString(String content)
    {
        State lState = state.get();

        this.size=content.length();
        this.contentConstructed=true;

        lState.string=content;
    }

    /**
     * Create string of given size defaulted to specified content.
     */
    public ClarionString(char content[])
    {
        State lState = state.get();

        this.size=content.length;
        lState.chararray=content;
        lState.len=content.length;
    }
    
    public String toString()
    {
        State lState = state.get();
        
        if (lState.string==null) { 
            if (lState.chararray!=null) {
                lState.string=new String(lState.chararray,0,lState.len);
            } else if (size==-1) {
                lState.string="";
            }
        }
        return lState.string;
    }
    
    /**
     * clone string
     * 
     * @return
     */
    public ClarionString like()
    {
        ClarionString cs = new ClarionString();
        cs.encoding=this.encoding;
        cs.size=this.size;
        cs.state.get().string=this.toString();
        return cs;
    }
    
    private void getChars(int begin,int end,char dest[],int d_ofs)
    {
        State lState = state.get();
        
        if (lState.chararray!=null) {
            System.arraycopy(lState.chararray,begin,dest,d_ofs,end-begin);
            return;
        }
        
        if (lState.string!=null) {
            lState.string.getChars(begin,end, dest,d_ofs);
            return;
        }
    }
    
    
    private char charAt(int ofs)
    {
        State lState = state.get();

        if (lState.chararray!=null) {
            if (ofs<0 || ofs>=lState.len) return 0;
            return lState.chararray[ofs];
        }
        
        if (lState.string!=null) {
            if (ofs<0 || ofs>=lState.string.length()) return 0;
            return lState.string.charAt(ofs);
        }
        return 0;
    }
    
    private int getLength()
    {
        State lState = state.get();

        if (lState.chararray!=null) {
            return lState.len;
        }
        if (lState.string!=null) {
            return lState.string.length();
        }
        return 0;
    }
    
    int getCharArraySize()
    {
        State lState = state.get();
        if (lState.chararray==null) return -1;
        return lState.chararray.length;    	
    }

    private void shrinkCharArray()
    {
        State lState = state.get();
        if (lState.chararray==null) return;
        
        int optimalSize=lState.len*2+128;
        if (optimalSizelen?size:len];
                lState.len=0;
            } else {
                lState.chararray=lState.string.toCharArray();
                lState.len=lState.chararray.length;
            }
        }
        
        if (lState.chararray.lengthlen?size:len];
            if (cloneIfTooSmall) {
                System.arraycopy(lState.chararray,0,t,0,lState.len);
            } else {
                lState.len=0;
            }
            lState.chararray=t;
            lState.string=null;
            return true;
        }
        return false;
    }

    private boolean writeAndReportChange(ClarionString source,int len)
    {
        return writeAndReportChange(0,source,len);
    }
    
    private boolean writeAndReportChange(int ofs,ClarionString source,int len)
    {
        State lState = state.get();

        boolean change=getCharArray(ofs+len,false);
        if (!change && !isAnyoneInterestedInChange()) change=true; 

        lState.len=ofs+len;
        
        if (!change) {
            while (len>0) {
                len--;
                char c = source.charAt(len);
                if (c!=lState.chararray[ofs+len]) {
                    change=true;
                    lState.chararray[ofs+len]=c;
                    break;
                }
                lState.chararray[ofs+len]=c;
            }
        }
        if (len>0) {
            source.getChars(0,len,lState.chararray,ofs);
        }
        
        return change;
    }
    
    @Override
    public void setValue(ClarionObject object) {
        State lState = state.get();
        
        ClarionString st = null;
        if (object!=null) {
            st = object.getString();
        } else {
            st=new ClarionString();
        }

        int allowedSize=allowedSize();

        if (allowedSize==-1) {
            // when size of recipient string is not restricted
        	int o_len = getLength();
            if (writeAndReportChange(st,st.getLength())) {
                lState.string=null;
                notifyChange();
            } else {
                if (lState.string!=null && lState.len!=lState.string.length()) {
                    lState.string=null;
                    notifyChange();
                } else if (o_len != getLength()) {
                	notifyChange();
                }
            }
            shrinkCharArray();
            return;
        }

        if (encoding==STRING) {
            getCharArray(0,false);
            if (st.getLength()allowedSize) {
            if (writeAndReportChange(st,allowedSize)) {
                lState.string=null;
                notifyChange();
            }
            return;
        } else {
            
            boolean lengthChange = encoding!=STRING && getLength()!=st.getLength();
            
            if (writeAndReportChange(st,st.getLength())) {
                lState.string=null;
                notifyChange();
            }
            
            if (lengthChange) {
                lState.string=null;
                notifyChange(); 
            }
            
            return;
        }
    }

    @Override
    public ClarionObject add(ClarionObject object) {
        return toNumber().add(object);
    }

    @Override
    public int compareTo(ClarionObject object) {
        
        object=object.getValue();
        
        if (object instanceof ClarionNumber || object instanceof ClarionDecimal || object instanceof ClarionReal || object instanceof ClarionBool) 
        {
            boolean numeric=true;
            
            for (int scan=getLength()-1;scan>=0;scan--) {
                char c=charAt(scan);
                if (c!=' ' && c!=0) {
                    numeric=false;
                }
            }
            if (!numeric) numeric=isNumeric(true,false);
            
            if (numeric) {
                if (isDecimal()) {
                    return getDecimal().compareTo(object);
                } else {
                    return getNumber().compareTo(object);
                }
            }
        }
        
        ClarionString r = object.getString();
        
        int l1 = getLength();
        int l2 = r.getLength();
        
        while (l1>0) {
            char f = charAt(l1-1);
            if (f!=' ') break;
            l1--;
        }

        while (l2>0) {
            char f = r.charAt(l2-1);
            if (f!=' ') break;
            l2--;
        }
        
        
        int min = l1;
        if (l20) return 1;
        }
        
        if (l1l2) return 1;
        
        return 0;
    }

    @Override
    public int intValue() {
        if (isNumeric(false)) {
            try {
                return Integer.parseInt(toString().trim());
            } catch (NumberFormatException ex) { 
                return 0;
            }
        }
        if (isNumeric(true)) {
            
            String bit = toString();
            int dot = bit.indexOf('.');
            if (dot==0) return 0;
            try {
                return Integer.parseInt(bit.substring(0,dot).trim());
            } catch (NumberFormatException ex) { 
                return 0;
            }
        }
        return 0;
    }

    @Override
    public ClarionObject multiply(ClarionObject object) 
    {
        return toNumber().multiply(object);
    }

    /**
     *  Set external name for the object - used by external systems like SQL driver
     * @param name
     * @return
     */
    public ClarionString setName(String name)
    {
        doSetName(name);
        return this;
    }


    /**
     * return 1d array clone of the object based on size. Note java object
     * array returned is one larger than specified as clarion starts arrays at
     * offset '1' whilst java as offset '0'
     */ 
    public ClarionArray dim(int size)
    {
    	return new ClarionArray(this,size);
    }
    

    
    /**
     * Set string encoding. A few options exists
     * 
     * String - fixed width
     * pstring - fixed width memory footprint but first char specifies string length 
     * cstring - fixed width memory footprint - 0x00 terminated string 
     * astring - no idea 
     * 
     * @param encoding
     * @return
     */
    public ClarionString setEncoding(int encoding)
    {
        State lState = state.get();

        if (contentConstructed) {
            size++; // resize in memory
        } else {
            if (encoding!=STRING) {
                lState.string=null;
                lState.chararray=new char[0];
                lState.len=0;
            }
        }
        this.encoding=encoding;
        return this;
    }

    /**
     * Overlay string onto another clarion object
     * 
     * @param o
     * @return
     */
    public ClarionString setOver(ClarionMemoryModel o)
    {
        doSetOver(o);
        return this;
    }


    @Override
    public ClarionObject divide(ClarionObject object) {
        return toNumber().divide(object);
    }

    @Override
    public ClarionBool getBool() {
        return new ClarionBool(boolValue());
    }

    @Override
    public ClarionNumber getNumber() {
        return new ClarionNumber(intValue());
    }

    @Override
    public ClarionReal getReal() {
        return new ClarionReal(toString().trim());
    }

    @Override
    public ClarionString getString() {
        return this;
    }

    @Override
    public ClarionObject modulus(ClarionObject object) {
        return toNumber().modulus(object);
    }

    @Override
    public ClarionObject power(ClarionObject object) {
        return toNumber().power(object);
    }

    @Override
    public ClarionObject subtract(ClarionObject object) {
        return toNumber().subtract(object);
    }

    @Override
    public boolean boolValue() {
        for (int scan=0;scan0) {
            if (encoding==STRING) {
                fill='\u00ff';
            } else {
                if (size==-1) {
                    size=1;
                }
                fill='\u00ff';
            }
        }
        
        if (zero || size<=0) {
            setValue("");
        } else {
            char nstring[]=new char[size];
            for (int i=0;it) throw new IllegalArgumentException("Index invalid"); 
        if (t>size && size>=0) throw new IllegalArgumentException("Index invalid");

        ClarionString s = string.getString();
        int size = t-f+1;
        f--;
        t--;
        boolean change = getCharArray(t+1,true);
        while (lState.len<=t) {
            lState.chararray[lState.len++]=' ';
        }

        int l = lState.len;
        
        if (size>s.getLength()) {
            if (writeAndReportChange(f,s,s.getLength())) change=true;
            size-=s.getLength();
            f+=s.getLength();
        } else {
            if (writeAndReportChange(f,s,size)) change=true;
            size=0;
        }

        lState.len=l;
        
        while (size>0) {
            if (!change && lState.chararray[f]!=' ') {
                change=true;
            }
            lState.chararray[f]=' ';
            f++;
            size--;
        }
        lState.string=null;
        if (change) {
            notifyChange();
        }
    }

    public void setStringAt(Object from,Object to,Object string) {
        setStringAt(
                Clarion.getClarionObject(from),
                Clarion.getClarionObject(to),
                Clarion.getClarionObject(string));
    }
    
    /**
     *  Return source string repeated upto 255 characters
     *  
     * @param source
     * @return
     */
    public ClarionString all()
    {
        return all(255);
    }

    /**
     *  Return this string repeated upto src characters long
     *  
     * @param source
     * @return
     */
    public ClarionString all(int src)
    {
        if (getLength()==0) throw new IllegalStateException("Cannot report empty string");
        
        char result[]=new char[src];
        
        int scan=0;
        while (scanresult.length) scanTo=result.length;
            
            getChars(0,scanTo-scan,result,scan);
            scan=scanTo;
        }
        return new ClarionString(result);
    }
    
    
    @Override
    public ClarionObject negate() {
        return toNumber().negate();
    }

    /**
     * Return ascii character value of string (assume 1 char string)
     * @return
     */
    public int val()
    {
        if (getLength()>0) return charAt(0);
        return 32;
    }

    /**
     * Return true is string encodes a legitimate numeric
     * @return
     */
    public boolean isNumeric()
    {
        return isNumeric(true);
    }

    /**
     * return true if string is upper case. If string length>1 then
     * only test first char 
     * 
     * @return
     */
    public boolean isUpper()
    {
        if (getLength()==0) return false;
        char c = charAt(0);
        return (c>='A' && c<='Z');
    }

    /**
     * return true if string is only alpha characters.If string length>1 then
     * only test first char 
     * @return
     */
    public boolean isAlpha()
    {
        if (getLength()==0) return false;
        char c = charAt(0);
        return ( (c>='A' && c<='Z') || (c>='a' && c<='z') );
    }

    @Override
    public ClarionDecimal getDecimal() {
        return new ClarionDecimal(toString().trim());
    }

    /**
     * Format string as per specified picture formatter
     * 
     * @param aFormat
     * @return
     */
    public ClarionString format(String aFormat)
    {
        Formatter f = Formatter.construct(aFormat);
        return new ClarionString(f.format(toString()));
    }
    
    /**
     * Remove formatting characters from format
     * 
     * For numeric - remove everything but numeric formatters
     * 
     * For date, convert back to date format
     * 
     * For time, convert back to time format
     * 
     * For picture/key tokens return placeholder tokens
     * 
     * @param aFormat
     * @return
     */
    public ClarionString deformat(String aFormat)
    {
        Formatter f = Formatter.construct(aFormat);
        return new ClarionString(f.deformat(toString()));
    }

    /**
     * Get string length - result dependent upon encoding system used
     * 
     * @return
     */
    public int len() {
        if (encoding==STRING && size>=0) {
            return size;
        }
        return getLength();
    }

    /** 
     * Convert ascii code into a string
     * 
     * @param ascii
     * @return
     */
    public static ClarionString chr(int ascii) {
        return new ClarionString(String.valueOf((char)ascii));
    }

    /**
     * Return string left shifted.
     * 
     * @param offset - length of resultant string. 
     * @return
     */
    public ClarionString left(int offset)
    {
        char c[] = new char[offset];
        
        int len = getLength();
        int start =0;
        while (startoffset) len=offset+start;
        getChars(start,len,c,0);
        
        for (int scan=len-start;scanoffset) start=len-offset;
        
        // RIGHT...  (start=0, len=5, offset=8)
        // ...RIGHT  
        getChars(start,len,c,offset-len+start);
        
        for (int scan=offset-len+start-1;scan>=0;scan--) {
            c[scan]=' ';
        }
     
        return new ClarionString(c);
    }

    /**
     * return right justified string
     * 
     */
    public ClarionString right()
    {
        return right(len());
    }

    /**
     * Return center justified string
     * @param offset resultant length of new string
     * @return
     */
    public ClarionString center(int offset)
    {
        char c[] = new char[offset];
        
        int len = getLength();
        int start =0;
        while (startoffset) {
            len=offset+start-c_offset;
        }
        
        getChars(start,len,c,c_offset);
        
        // left pad
        for (int scan=0;scansize && size>=0) t=size;
        
        return stringAt(new ClarionNumber(f),new ClarionNumber(t));
    }

    /**
     * Return uppercased string
     * @return
     */
    public ClarionString upper()
    {
        return new ClarionString(toString().toUpperCase());
    }

    /**
     * Return lower cased string
     * @return
     */
    public ClarionString lower()
    {
        return new ClarionString(toString().toLowerCase());
    }

    /**
     * return clipped string - trailing whitespace removed. 
     * @return
     */
    public ClarionString clip()
    {
        int len=getLength();
        
        if (len==0) return this;
        if (charAt(len-1)!=' ') {
            return this;
        }
        len--;
        
        while (len>0) {
            if (charAt(len-1)!=' ') break;
            len--;
        }
        return new ClarionString(toString().substring(0,len));
    }

    /**
     * Find position of string in another string
     * @param search
     * @return
     */
    public int inString(String search) {
        return inString(search,search.length(),1);
    }

    /**
     * find position of a string in another string
     * @param search
     * @param step
     * @param ofs
     * @return
     */
    public int inString(String search,int step,int ofs) {
        
        int scan=ofs-1;
        while (scan+search.length()<=getLength()) {
            boolean match=true;
            for (int ss=0;ss)o;
            } else {
            	throw new RuntimeException("Address invalid:"+o);
            }
            notifyChange();
            return;
        }
        throw new RuntimeException("Not yet implemented");
    }
    
    private void serializeString(CMem is)
    {
        int len = getLength();
        for (int scan=0;scan0) {
            is.writeChar(pad);
            len--;
        }
    }

    @Override
    public void serialize(CMem is)
    {
        
        if (encoding==STRING) {
            serializeString(is);
            return;
        }

        if (encoding==PSTRING) {
            is.writeByte(getLength());
            serializeString(is);
            serializePadding(is,' ',size-1-getLength());
            return;
        }
        
        if (encoding==CSTRING) {
            serializeString(is);
            is.writeByte(0x00);
            serializePadding(is,' ',size-1-getLength());
            return;
        }

        if (encoding==ASTRING) {
        	is.writeObject(state);
            return;
        }
        
        // ASTRING Encoding not yet supported
        throw new RuntimeException("Not yet implemented:"+encoding);
    }

    public ClarionString stringAt(ClarionObject o)
    {
        int pos = o.intValue();

        if (pos<=0) return new ClarionString(" ");
        if (pos>getLength()) return new ClarionString(" ");
        return new ClarionString(toString().substring(pos-1,pos));
    }
    
    public ClarionString stringAt(ClarionObject from,ClarionObject to)
    {
        int f = from.intValue();
        int t = to.intValue();

        if (f<=0) f=1;
        if (tsize && size>=0) t=size;
        
        char result[] = new char[t-f+1];
        
        //int copy=result.length;
        if (t>getLength()) {
            getChars(f-1,getLength(),result,0);
            for (int scan=getLength()-(f-1);scan0) {
            char f = charAt(0);
            if (f=='-') scan++;
        }
        
        if (getLength()==scan) return false;
        char f = charAt(scan);
        if (f<'0' || f>'9') {
            if (f!='.' || !allowDecimal) return false;
        }
        
        int lead=0;
        int trail=0;
        
        while (scan'9') break;
            lead++;
            scan++;
        }

        if (mustDecimal && scan==getLength()) return false;
        
        if (allowDecimal && scan'9') break;
                    trail++;
                    scan++;
                }
            } else {
                if (mustDecimal) return false;
            }
        }

        if (lead==0 && trail==0) return false;
        
        while (scan0) {
            char c = in.charAt(last-1);
            if (c!=' ') break;
            last--;
        }
        if (last==in.length()) return in;
        return in.substring(0,last);
    }

    @Override
    public void notifyChange() {
        State lState = state.get();

        if (encoding==ClarionString.CSTRING) {
            for (int scan=0;scan0?pattern.charAt(scan-1):0;
            char c = pattern.charAt(scan);
            
            if (c=='{' && lastChar!='\\' && !inBlock) {
                sb.append('(');
                continue;
            }
            
            if (c=='}' && lastChar!='\\' && !inBlock) {
                sb.append(')');
                continue;
            }
            
            if (c=='[' && lastChar!='\\') {
                inBlock=true;
            }
            if (c==']' && lastChar!='\\') {
                inBlock=false;
            }
            if (c=='(' && !inBlock && lastChar!='\\') {
                sb.append('\\');
            }
            if (c==')' && !inBlock && lastChar!='\\') {
                sb.append('\\');
            }
            sb.append(c);
        }
        return Pattern.compile(sb.toString(),ignoreCase ? Pattern.DOTALL+Pattern.CASE_INSENSITIVE : Pattern.DOTALL);
    }
    
    public int strpos(String regex)
    {
        return strpos(regex,false);
    }
    
    public int strpos(String regex,boolean mode)
    {
        Pattern p = getClarionRegEx(regex,mode);
        Matcher m = p.matcher(toString());
        if (m.find()) {
            return m.start()+1;
        }
        return 0;
    }

    public boolean match(String regex)
    {
        return match(regex,Match.WILD);
    }
    
    public boolean match(String regex,int mode)
    {
        boolean ignoreCase=false;
        if ( (mode & Match.NOCASE)!=0 ) {
            ignoreCase=true;
            mode=mode^Match.NOCASE;
        }
        
        switch(mode) {
            case Match.SIMPLE:
                if (ignoreCase) {
                    return toString().equalsIgnoreCase(regex);
                } else {
                    return toString().equals(regex);
                }
            case Match.WILD:
                return getClarionWildCardEx(regex,ignoreCase).matcher(toString()).find();
            case Match.REGULAR:
                return getClarionRegEx(regex,ignoreCase).matcher(toString()).find();
            default:
                throw new RuntimeException("Not supported");
        }
    }
    
    public String quote()
    {
        return quote(false);
    }
    
    /**
     * Quote. See clarion 6 doco. What exactly this does will remain a mystery
     * 
     * when mode is false - generate clarion encoded string. Clarion 6 is actually buggy,
     * this implementation mirrors that bugginess perfectly
     * 
     * When mode is true - do something strange and unexplicable. See clarion 6 doco
     * 
     * @param mode
     * @return
     */
    public String quote(boolean mode)
    {
        StringEncoding enc = new StringEncoding(toString());
        while (enc.remaining()>0) {
            char c = enc.read();
            if (c=='<') {
                if (!enc.matches(Constant.binaryPattern,-1)) {
                    if (!mode) enc.append(c);
                } else {
                    if (mode) {
                        // do something weird - perform clarion escape
                        Constant.encodeBinary(enc,false);
                        continue;
                    }
                }
            }
            if (c=='{') {
                if (!enc.matches(Constant.repeatPattern,-1)) {
                    if (!mode) enc.append(c); 
                } else {
                    if (mode && enc.pos()>1) {
                        // do something weird - perform clarion escape
                        int repeat = Constant.getRepeat(enc);
                        char last = enc.peek(-2);
                        while (repeat>1) {
                            enc.append(last);
                            repeat--;
                        }
                        continue;
                    }
                }
            }
            if (c=='\'') {
                enc.append(c);
            }
            enc.append(c);
        } 
        return enc.getBuilder().toString();
    }

    public String unquote()
    {
        StringEncoding enc = new StringEncoding(toString());
        while (enc.remaining()>0) {
            char c = enc.read();
            if (c=='\'' || c=='{' || c=='<') {
                if (enc.remaining()>0 && enc.peek(0)==c) {
                    enc.read(); // skip
                }
            }
            enc.append(c);
        }
        return enc.getBuilder().toString();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy