Sunday, April 15, 2012

Simple plugin architecture with java.util.ServiceLoader

Suppose you want to implement some kind of plug-in architecture. Java has simple mechanism for that. First of all, you have to think of an interface which will provide the functionality of a plugin, in Java terms it's called a service:
package com.example;

public interface CodecService {
    Encoder getEncoder(String charset);
    Decoder getDecoder(String charset);
}
This interface should somehow allow to discover if specific instance supports specific request. For example, it can return null if it doesn't. Next, you have to create concrete instances of the service, called service providers:
package com.example.jp;
// ...

public class JapaneseCodecServiceImpl implements CodecService {
    public Encoder getEncoder(String charset) {
        if (! "EUC-JP".equals(charset)) {
            // not supported
            return null;
        }
        return new JapaneseEncoder(charset);
    }
    // ...
}
Supposedly this class will reside in a different JAR than the service itself. Also, to enable automatic discovery of the service provider you'd have to create a file named META-INF/services/com.example.CodecService (obviously, the name will change from service to service). This file should contain one line for each provider with the provider fully qualified class name in it:
# comments are allowed
com.example.jp.JapaneseCodecServiceImpl
com.example.cyr.CyrillicCodecServiceImpl
A common practice is to bundle these files with the service providers into a single JAR. Now you have to create an access point to your service. This includes using java.util.ServiceLoader class. This class is parametrized with a service interface. You should create one single instance of that class for each service in your access point code:
public class CodecManager {
    private static ServiceLoader serviceLoader = ServiceLoader.load(CodecService.class);
    // ...   
}
After that, you can add a static method which will serve user's request using discovered service providers. This method will use ServiceLoader.iterator method (or make use of ServiceLoader being Iterable), which firstly iterates over cached providers and then, if you didn't stop iterating, will lazily load discovered providers one by one:
public class CodecManager {
    private static ServiceLoader serviceLoader = ServiceLoader.load(CodecService.class);
    
    public static Encoder getEncoder(String charset) {
        synchronized (serviceLoader) {
            for (CodecService cs : serviceLoader) {
                Encoder e = cs.getEncoder(charset);
                if (e != null) {
                    return e;
                }
            }
        }
    }

}
Note that, according to the documentation, ServiceLoader is not thread-safe. Although this same documentation shows an example without any thread safety means in place.

Wednesday, May 5, 2010

FIND+GREP replacement for PowerShell

It's pretty common in UNIX world to use find in conjunction with grep for searching filesystem tree for something. Would it be searching for specific class in source code tree:


$ find -name \*.cpp -o -name \*.hpp -exec grep -Hb class {} \;

Or just searching for some word in your notes, doesn't matter, but it is really common. So what do we have in PowerShell world to substitute those commands?

First of all, let's try to replace grep. For this we have cmdlet called Select-String, or sls (an alias). Let's try something simple:


$ sls class *.hpp

CustomTimeEdit.hpp:5:class CustomTimeEdit : public QTimeEdit {
DaySelecter.hpp:12: class Model : public QAbstractItemModel
DaySelecter.hpp:43: class View : public QTreeView
[...]

For some reason, documentation got this messed up, saying path should go first, and pattern second, but anyway.

What if we want to search files, whose names match several patterns (for example, cpp and hpp files):


$ sls DaySelector *.hpp,*.cpp

DaySelecter.hpp:1:#ifndef __DAYSELECTER_HPP__
DaySelecter.hpp:2:#define __DAYSELECTER_HPP__
DaySelecter.hpp:10:namespace DaySelecter {
[...]

What if we want to match several patterns in those files?


$ sls DaySelector,MainWindow *.hpp,*.cpp


DaySelecter.hpp:1:#ifndef __DAYSELECTER_HPP__
DaySelecter.hpp:2:#define __DAYSELECTER_HPP__
DaySelecter.hpp:10:namespace DaySelecter {
MainWindow.hpp:1:#ifndef __MAINWINDOW_HPP__
MainWindow.hpp:2:#define __MAINWINDOW_HPP__
MainWindow.hpp:4:#include
[...]

The fact is, for both pattern and path Select-String accepts arrays (which in PowerShell are specified with comma operator).

Now you remember PowerShell operates with objects, not text, yes? So let's see, what those objects are:


$ sls DaySelector *.hpp,*.cpp | gm

TypeName: Microsoft.PowerShell.Commands.MatchInfo

Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
RelativePath Method string RelativePath(string directory)
ToString Method string ToString(), string ToString(string directory)
Context Property Microsoft.PowerShell.Commands.MatchInfoContext Context {get;set;}
Filename Property System.String Filename {get;}
IgnoreCase Property System.Boolean IgnoreCase {get;set;}
Line Property System.String Line {get;set;}
LineNumber Property System.Int32 LineNumber {get;set;}
Matches Property System.Text.RegularExpressions.Match[] Matches {get;set;}
Path Property System.String Path {get;set;}
Pattern Property System.String Pattern {get;set;}

Not just we can look at the output, we can cut the pieces of output with ease (no more sed scripts for command output parsing!), we can analyze output easily. That's pretty exciting, I think. That's the future of command-line. But I'm off the road.

I think, you've got the idea. With sls you can perform regexp matching (default behaviour), simple matching (-SimpleMatch), search for just first match in file, ignoring all others (-List), or, on the contrary, search for all matches, even if several are present on the same line (-AllMatches), do negative matching (-NotMatch). You can also obtain results in context of several lines forward and backward (-Context option), pretty like diff -u does. What's more, you can even specify different encoding, though this list, for some reason, is far from complete: it contains only Unicode encodings, ANSI and OEM, which is sufficient in most cases, but the list could have been more diverse.

Now what about searching the tree. In PowerShell, we use Get-ChildItem, or ls. I don't know full powers of UNIX ls, but PowerShell ls is pretty powerful thing. We can find all files in the tree, matching specific patterns:


$ ls -r -inc *.cpp,*.hpp

Or we can find files, not matching pattern:


$ ls -r -ex *.hpp~

Or we can even do some crazy stuff, matching some files and then
excluding some files, not matching specific pattern:


$ ls -r -inc *.cpp,*.hpp -ex DaySelecter*

If you want more control over matching, you can process matching files with Where-Object block -- remember? PowerShell operates on objects, not text:


$ ls -r -inc *.cpp,*.hpp -ex DaySelecter* | ? { $_.IsReadOnly }

Ok, now, how we can use powers of ls with sls? The answer is, sls can accept files not only from command line, but from pipe as well, so we can just pipe both commands together, and get the result desired:


$ ls -r -inc *.cpp,*.hpp -ex *DaySelecter* | sls DaySelecter

MainWindow.cpp:26: connect(viewDaySelecter->selectionModel(),
MainWindow.cpp:38: viewDaySelecter->setDiary(diaryModel);

Now that's some powers we need!

Wednesday, March 31, 2010

Configuring GNU Emacs 23 + Ispell + Russian Dictionary + Windows

Emacs ispell package is capable of running both Ispell and Aspell, but
as far as I've tried Aspell, it gives some nonsensical results. Maybe this
was due to some misconfiguration, but I wasn't eager to sort it out.

So I went with Ispell. Configuring it is also a bit of obscure process, so
I thought it would be good to have an installation guide step by step:


  1. Download Ispell from here.


  2. You should have Cygwin installed on your machine. Let's say, it is in folder c:\Programs\Cygwin.


  3. Unpack aforementioned Ispell to your Cygwin directory.


  4. Go to c:\Programs\Cygwin\usr\local\bin and copy ispell.exe to c:\Programs\Cygwin\bin. This is needed to run ispell directly from Windows, bypassing Cygwin shells.


  5. Download russian dictionaries from here and unpack them to some temporary folder. Let's say it would be d:\temp.


  6. Run Cygwin shell and perform the following commands (modify them appropriately):


    cd /cygdrive/d/temp
    make win
    mv russian.hash russianw.hash
    mv russian.aff russianw.aff
    mv russianw.* /usr/local/lib


    From now on, you sould be able to check files encoded in windows-1251 charset directly under Cygwin, like that:


    LANG=ru_RU ispell -drussianw file.txt


    Or, under cmd.exe:


    set LANG=ru_RU.CP1251
    c:\Programs\Cygwin\bin\ispell.exe -drussianw file.txt




  7. Now all we need to do is to configure Emacs correctly. Add the following lines to your dotemacs:


    (setq ispell-program-name "c:\\Programs\\cygwin\\bin\\ispell.exe")
    (setq ispell-dictionary "russianw")




  8. This should do the task. Now restart emacs and try running ispell on some file.


Friday, July 4, 2008

Disable arrow keys in Emacs

So for this habit is ineradicable by will and my hands a little ache after hour or two of code surfing, I've decided just to disable this keys in my .emacs. Here we go:

(global-unset-key [(up)])
(global-unset-key [(down)])
(global-unset-key [(left)])
(global-unset-key [(right)])
(global-unset-key [(prior)])
(global-unset-key [(next)])
(global-unset-key [(home)])
(global-unset-key [(next)])

Saturday, November 3, 2007

Про C++

Плюсы -- подлый язык. Когда пишешь на C -- ставишь во главу стола производительность, когда пишешь на чем-то более высокоуровневом (будь то Python, или, прости господи, Lisp) -- пляшешь от ясного выражения концепций в конструкциях языка.

Когда же пишешь на плюсах, все время пытаешься усидеть на двух стульях разом (что вернуть -- список или итератор, или указатель? список чего? указателей или объектов? уф... и т.д. и т.п.) :( Может, с опытом это пройдет.

Thursday, September 27, 2007

Managing Oracle Forms CVS tree under Powershell

At work, I have to deal with Oracle Forms, which itself is a tool for creating data access forms in a visual way. This tool stores forms as a binary files (.fmb's), so managing them under ordinary version control system like CVS is a pain, because I can't see what changes were made to form (if any). In spite of that, CVS is my only available option.

Fortunately, Oracle Developer Suite has a tool, frmf2xml which can be used to convert binary FMB file to XML, so, gluing it all together (cvs, frmf2xml and ordinary diff utility) with PowerShell, I have written a script, Compare-CvsForms, which is much like cvs diff command, but works with theese binary forms. Actually, I've also written vdiff.py script, based on difflib, which is much more convenient for comparing such a messy data (XML, huh).

So, here is the Compare-CvsForms.ps1 script (I've put it to my PowerShell profile directory):


Param ([string]$formsFile)

if (cvs status $formsFile |
select-string 'Status: (Locally Modified|Needs Checkout|Needs Patch)')
{
# store working copy in another place
$local:temp_fmb = [System.IO.Path]::ChangeExtension(
[System.IO.Path]::GetTempFileName(), "fmb")
$local:temp_xml = [System.IO.Path]::ChangeExtension(
[System.IO.Path]::GetTempFileName(), "xml")
Move-Item -force $formsFile $local:temp_fmb
# produce xml of a working copy
frmf2xml $local:temp_fmb | out-null
if ($LastExitCode -ne 0) {
Echo "ERROR: Cannot convert working copy of form to XML!"
Move-Item -Force $local:temp_fmb $formsFile
return
}
# get repositary copy
cvs update 2>&1 | out-null
#produce xml of a repositary copy
frmf2xml $formsFile | out-null
if ($LastExitCode -ne 0) {
Echo "ERROR: Cannot convert CVS copy of form to XML!"
Move-Item -Force $local:temp_fmb $formsFile
Remove-Item -Force $xml_from
return
}
# compare xmls
$xml_from=$formsFile.substring(0, $formsFile.length-4) + "_fmb.xml"
$xml_to=$local:temp_fmb.substring(0, $local:temp_fmb.length-4) + "_fmb.xml"
vdiff.py -context $xml_from $xml_to
if ($LastExitCode -eq 0) {
$formsFile.toUpper() + ": Working copy does not differ from repositary."
}
Move-Item -Force $local:temp_fmb $formsFile
Remove-Item -Force $xml_to,$xml_from
} else {
$formsFile.toUpper() + ": Working copy is up to date."
}


And vdiff.py (pretty straightforward) script (surely, you should have Python installed, if not - you could just as easily replace this script with ordinary diff utility):


#!/usr/bin/python
import os
import sys
import difflib
import tempfile

program_name = os.path.splitext(os.path.basename(os.path.sys.argv[0]))[0].upper()
browser = "c:\\Program Files\\Internet Explorer\\iexplore.exe"

if len(sys.argv) < 3:
print "\nUSAGE:\n\n %s FROM-FILE TO-FILE\n" % program_name
sys.exit(2)

if sys.argv[1] == "-context":
context = True
from_fn = sys.argv[2]
to_fn = sys.argv[3]
else:
context = False
from_fn = sys.argv[1]
to_fn = sys.argv[2]

try:
from_lines = open(from_fn).readlines()
except IOError:
print "%s: Error while reading file -- %2" % (program_name, from_fn)
sys.exit(1)

try:
to_lines = open(to_fn).readlines()
except IOError:
print "%s: Error while reading file -- %2" % (program_name, to_fn)
sys.exit(1)

differs = False
for x in difflib.context_diff(from_lines, to_lines):
differs = True
break

if differs:
diff = difflib.HtmlDiff(wrapcolumn=70).make_file(from_lines, to_lines, from_fn, to_fn, context)
diff = diff.replace("<head>",
'<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">')
tmp = tempfile.mkstemp(".html")
os.write(tmp[0], diff)
os.close(tmp[0])
os.spawnl(os.P_WAIT, browser, ' file://' + tmp[1] + '')
sys.exit(1)
else:
sys.exit(0)


Scripting with PowerShell actually is a great deal of fun :)

Friday, June 15, 2007

A little correction to Process-File function

Once after processing my C++ source code tree with Process-File my sources became uncompilable. The problem was caused by redirection operator `>', which transformed source files from plain-old ASCII to Unicode. So there's corrected version which uses Out-File cmdlet instead with encoding specified explicitly:


function Process-File([scriptblock]$script,
$filename,
$encoding = "ascii") {
$local:temp = New-TempFileName
Get-Content -Encoding $encoding $filename | `
Foreach $script | `
out-file -encoding $encoding $local:temp
Move-Item -force $local:temp $filename
}