Язык программирования Java для многих известен именно благодаря своим апплетам - особым программам, которые не могут нанести никакого вреда компьютеру на котором исполняются. Это огромное облегчение для пользователя - знать, что вот эта закачанная из Интернета программа гарантированно не является разносчиком вирусов.
Для обеспечения этих гарантий апплет сильно ограничен в своих правах - он, даже будучи запущенным с пользовательского компьютера, не может не только сохранять, но даже и читать файлы с диска этого же компьютера!
Хорошо, это гарантирует неприкосновенность и конфедициальность частных данных.
Но теперь ответьте, как такой программе сохранить свой регистрационный номер?
Почему именно shareware
выбрано в качестве примера решения этой неразрешимой задачи?
Этот способ продвижения программ весьма удобен для пользователя, он давно и успешно применяется за рубежом компаниями всех весовых категорий, да и отечественный потребитель постепенно приучается платить.
К тому же здесь действует принцип "сначала пробую - затем плачу" - а это, согласитесь, намного лучше, чем покупка "кота в мешке" и долгие препирательства со службой тех.поддержки (наверняка каждый из читающих эти строки хоть раз в жизни приобретал лиценный диск, который тем не менее отказывался работать или работал с заметными ошибками). При этом, юридически производитель такого продукта прав - что и закреплено в прилагающемся лицензионном соглашении, которое каждый из нас, увы, должен сначала принять и только затем узнать "будет ли эта штука работать".
Лично я не удивлюсь, если однажды именно shareware станет единственной законной формой продажи программного обеспечения.
Это всё теория "в прекрасных стихах", а проза жизни такова, что пользователя так или иначе приходится (будем называть вещи своими именами) заставлять платить.
И самый распространённый способ для этого - необходимость получения пользователем регистрационного кода для полноценного пользования программой.
А теперь вспомним, что апплет не может ни сохранять, ни даже читать информацию с диска... а значит не сможет считать и тем более сохранить свой регистрационный код.
Да, задача определённо неразрешима...
Решение невозможного
Апплет будет использоваться не сам по себе - он обычно исполняется в браузере, а ещё точнее: апплет включается в web-страницу - то есть обычный html-файл - и вот уже этот файл просматривается через браузер, заставляя апплет исполняться.
Понимаете, об апплете не следует думать, как о "вещи в себе" - он и запускающий его html-файл это единый программный комплекс.
А какими возможностями обладают html-документы?
Нас будет интересовать возможность исполнения встраиваемых в web-страницу сценариев JavaScript.
Одной из возможностей JavaScript является способность сохранять информацию в cookie - специальных системных файлах. И одновременно, можно организовать взаимодействие сценария JavaScript с апплетом.
Так почему бы не сохранять необходимую апплету информацию в cookie при посреднической помощи сценария JavaScript?
Как готовят cookie
Приведу собственное решение. Оно заключается в своеобразной библиотеке cookie.js, которая легко подключается в любой html-документ строкой:
<script language="JavaScript" src="cookie.js"></script> |
Содержимое этой импровизированной библиотеки - пара функций, одна из которых сохраняет либо удаляет cookie (в зависимости от переданных ей параметров), а другая считывает cookie с подходящим именем. Вот их текст:
function saveCookie(szName, szValue, szDay){
var dtExpires=new Date()
var dtExpiryDate=""
if(szDay>0){//установить время хранения cookie в днях
dtExpires.setTime(dtExpires.getTime()+szDay*24*60*60*1000)
}else{//установить стирание cookie
dtExpires.setTime(dtExpires.getTime()-1)
}
dtExpiryDate=dtExpires.toGMTString()
document.cookie=szName + "=" + szValue + "; expires=" + dtExpiryDate
}
function loadCookie(szName){
var i=0
var nStartPosition=0
var nEndPosition=0
var szCookieString=document.cookie
while(i<=szCookieString.length){
nStartPosition=i
nEndPosition=nStartPosition + szName.length
if(szCookieString.substring(nStartPosition, nEndPosition) == szName){
nStartPosition=nEndPosition+1
nEndPosition=document.cookie.indexOf(";", nStartPosition)
if(nEndPosition < nStartPosition)
nEndPosition = document.cookie.length
return document.cookie.substring(nStartPosition, nEndPosition)
break
}
i++
}
return ""
}
|
Последняя функция считывает cookie с совпадающим именем и возвращает его содержимое. Хотя её текст выглядит весьма объёмным, никаких особых хитростей в себе он не таит. А вот меньшая по размеру функция создания cookie гораздо более интересна.
Если создаётся новый cookie с именем уже существующего - увы, старое содержимое будет безвозвратно затёрто без каких-либо предупреждений.
Cookie храниться в системе столько времени, сколько было определно при его создании (перезапись является созданием заново). Поэтому обдуманно отнеситесь к последнему входному параметру - ведь стоит установить уже минувшую дату и вместо создания Вы удалите запись!
Для удобства использования последний параметр вводится не в виде даты - а виде срока, иначе говоря числа дней. А дата вычисляется самой функцией. Причём если она меньше или равна 0 - то будет произведено удаление cookie.
Последней строкой в конце функции производится собственно вся работа: имя записи, затем её значение и, наконец, дата окончания хранения - все разделены точкой с запятой. Да, весь cookie - это одна запись, вдобавок число таких записей может быть ограничено системой - так что сохранить мы сможем не слишком много. Впрочем, для хранения регистрационных данных этих возможностей будет более чем достаточно.
С чем едят cookie
Быть может я не слишком внимательно читал официальную документацию по Java, но ответ на этот вопрос я нашёл не в ней, и не сразу, и не в одном месте, а по частям из разных источников, а кое-что и уточнил "методом научного тыка".
Если поле или метод апплета объявлены со спецификатором доступа public то оказывается сценарий JavaScript может обращаться к ним. Очевидно, что таким образом совсем нетрудно производить считывание информации.
Например, вот начало исходного текста апплета, содержащее объявления таких переменных:
import java.awt.*;
import java.applet.*;
public class Tarakan extends Applet implements Runnable{
//регистрационные переменные считываются и сохраняются в cookie
public String sName = null;
public String sNik = null;
public String sReg = null;
|
Для сохранения нажмите <input type="button"
value="Save"
onClick="saveCookie('TarakanKlugDevJugaRu',Tarakan.sName+'.'
+Tarakan.sNik+'.'+Tarakan.sReg+'.'+Tarakan.sLang+'.'+Tarakan.sLive+'.'
+Tarakan.sDinamit+'.'+Tarakan.sLevel+'.'+Tarakan.sLevelMaze+'.'
+Tarakan.sAllDoneMaze+'.'+Tarakan.sAllDoneDistance, 10*365)">
<! save it on 10 year>
|
Казалось бы, задача уже почти решена. Но если идёт о shareware, то бывает крайне желательно обеспечить программе знание того зарегистрирована она или нет сразу после запуска, уже на этапе инициализации - например, для вывода соответствующего приветствия зарегистрированному пользователю или же предупреждения для незарегистрированного.
На мой взгляд оптимальным решением в данном случае оказывается генерация фрагмента html-текста "на лету" при помощи сценария JavaScript. Фокус в том, что все браузеры, встречая, в процессе считывания html, включённый в страницу сценарий, тут же начинают исполнять его. И, если этот сценарий содержит директивы по вписыванию html-текста - то в загруженной странице такой текст окажется на том месте, где был размещён сценарий.
Значит, если этот html-текст содержит тег <applet> - то апплет мы и увидим на готовой странице. Если же не полениться добавить к этому тегу и сопутствующие теги с необходимыми параметрами (в нашем случае это регистрационные данные) - то таким образом, задача передачи сохранённых данных апплету будет решена.
<script language="JavaScript">
<!--
var u="u"
var n="n"
var r="r"
var la="0"
var li="1"
var di="0"
var le="0"
var lm="0"
var am="0"
var ad="0"
var nStartPosition=0
var nEndPosition=0
var szTemp=loadCookie("TarakanKlugDevJugaRu")
if(szTemp!=""){//else no save
nEndPosition = szTemp.indexOf(".", nStartPosition)
u = szTemp.substring(nStartPosition, nEndPosition)
nStartPosition = nEndPosition + 1
nEndPosition = szTemp.indexOf(".", nStartPosition)
n = szTemp.substring(nStartPosition, nEndPosition)
nStartPosition = nEndPosition + 1
nEndPosition = szTemp.indexOf(".", nStartPosition)
r = szTemp.substring(nStartPosition, nEndPosition)
nStartPosition = nEndPosition + 1
nEndPosition = szTemp.indexOf(".", nStartPosition)
la = szTemp.substring(nStartPosition, nEndPosition)
nStartPosition = nEndPosition + 1
nEndPosition = szTemp.indexOf(".", nStartPosition)
li = szTemp.substring(nStartPosition, nEndPosition)
nStartPosition = nEndPosition + 1
nEndPosition = szTemp.indexOf(".", nStartPosition)
di = szTemp.substring(nStartPosition, nEndPosition)
nStartPosition = nEndPosition + 1
nEndPosition = szTemp.indexOf(".", nStartPosition)
le = szTemp.substring(nStartPosition, nEndPosition)
nStartPosition = nEndPosition + 1
nEndPosition = szTemp.indexOf(".", nStartPosition)
lm = szTemp.substring(nStartPosition, nEndPosition)
nStartPosition = nEndPosition + 1
nEndPosition = szTemp.indexOf(".", nStartPosition)
am = szTemp.substring(nStartPosition, nEndPosition)
nStartPosition = nEndPosition + 1
nEndPosition = szTemp.length
ad = szTemp.substring(nStartPosition, nEndPosition)
}
document.write('<applet code=Tarakan.class name=Tarakan id=Tarakan width=440 height=440>')
document.write('<param name=user value="'+u+'">')
document.write('<param name=nik value="'+n+'">')
document.write('<param name=reg value="'+r+'">')
document.write('<param name=language value="'+la+'">')
document.write('<param name=live value="'+li+'">')
document.write('<param name=dinamit value="'+di+'">')
document.write('<param name=level value="'+le+'">')
document.write('<param name=levelmase value="'+lm+'">')
document.write('<param name=donemaze value="'+am+'">')
document.write('<param name=distance value="'+ad+'"></applet>')
}
//-->
</script>
|
Регистрационная форма
Кажется уже всё и так очевидно - и всё же есть ещё некоторые идеи, которые могут быть полезны.
Обычные программы содержат форму или окно для ввода регистрационных данных внутри себя. Но в случае апплета это может оказаться не самой лучшей идеей.
Java хорош компактностью - как исходного текста, так и конечного исполняемого кода. Существует история о том, как некий человек создал очень полезную по его мнению программу, умевшую показывать IP-адрес пользователя - но вот беда, он написал её на VB и получился монстр более мегабайта весом! Говорят, при известной ловкости, на C++ можно написать аналогичную утилиту размером всего в десятки килобайт. Это кажется превосходным результатом - но только до тех пор, пока вы не познакомитесь с Java.
Немаловажным достоинством Вашего апплета может быть именно его компактность - ведь это и скорость открытия содержащей его страницы (представьте себе пользователя с модемом - это всё ещё не редкость). В этом случае загромождать изящную программку формой для регистрации (исполняющейся всего однажды!) может показаться непозволительной растратой тех самых ресурсов, за экономию которых Вы столько боролись в процессе создания своего творения.
Вспомните и о том, что сам апплет не может начать процесс сохранения информации в cookie - а значит не избежать раздражённыз возгласов "я всё ввел правильно, но Ваша программа вновь требует от меня ввод пароля!" (и в такой ситуации будет очень трудно объяснить человеку, что нужно было нажать ещё одну кнопку - вне апплета).
На мой взгляд лучшим решением здесь является отдельная регистрационная web-страница, ссылку на которую можно разместить прямо на странице с апплетом. Ведь cookie не привязывается к конкретной странице - мы вправе создать его на одной, а затем закрыть её (чтоб больше никогда к ней не возвращаться) и пользоваться нужными нам данными на нужных нам страницах.
Вот пример подобной регистрационной формы:
<HTML><meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<HEAD><TITLE>Tarakan registrator</TITLE>
<script language="JavaScript" src="cookie.js"></script>
</HEAD><BODY>
<h4 align=center>Регистрация</h4>
<form name="Person">
<table border=3 bgcolor=#cccccc align=center><tr><td>
<table border=0 bgcolor=#cccccc align=center>
<tr><th>Имя (пользователя):</th><td colspan=2><input type="text" name="user" value="" size="50"
onChange="this.value=this.value"
onFocus="this.select()"></td></tr>
<tr><th>Кличка (таракана):</th><td colspan=2><input type="text" name="nik" value="" size="50"
onChange="this.value=this.value"
onFocus="this.select()"></td>
<tr><th>Код (регистрации):</th><td><input type="text" name="reg" value="" size="7"
onChange="this.value=this.value"
onFocus="this.select()"></td>
<td><input type="button" value="Save"
onClick="saveCookie('TarakanKlugDevJugaRu', user.value+'.'+nik.value+'.'+reg.value+'.0.1.0.0.0.0.0', 10*365);">
<! сохранить на 10 лет>
</td></tr>
</table>
</td></tr></table>
</form>
</BODY></HTML>
|
Регистрационный код
Откуда возьмёт регистрационный код пользователь понятно - мы сообщим. А мы откуда его возьмём?
Нам нужен кодогенератор - и оказывается в Java уже имеется для этого всё необходимое.
Приведу пример реализации в виде апплета:
import java.awt.*;
import java.applet.*;
public class TarakanCode extends Applet{
private TextField tfName;
private TextField tfNik;
private TextField tfCode;
public void init(){
this.setBackground(Color.lightGray);
this.setForeground(Color.black);
this.setLayout(new GridLayout(4, 2));
Label labelName = new Label("Name (user)");
tfName = new TextField(40);//для ввода имени, отчества, фамилии
tfName.setEditable(true);
Label labelNik = new Label("Nikname (tarakan)");
tfNik = new TextField(40);//вряд ли кличка будет столь длинной
tfNik.setEditable(true);
Label labelCode = new Label("Code (result)");
tfCode = new TextField(7);//здесь будет показан код
tfCode.setEditable(false);//чтобы отличалось от полей ввода
// на самом деле предумышленно испортить сгенерированный код вводом в его поле нельзя
// потому что сейчас попытка ввода любого символа куда угодно вызывает пересчёт кода
Label labelWarning0 = new Label("Все символы:");//предупреждения-рекомендации
Label labelWarning1 = new Label("цифры или латынь!");//для оператора
add(labelName);
add(tfName);
add(labelNik);
add(tfNik);
add(labelCode);
add(tfCode);
add(labelWarning0);
add(labelWarning1);
}//init
public boolean handleEvent(Event event){
if(event.id == Event.KEY_RELEASE && event.modifiers != Event.CTRL_MASK){
//предположительно были введены новые значения в текст.поля
//на всякий случай обсчитать все - хуже от этого не будет
calcCode();
}else return false;//вернуть что это сообщение не обработано
return super.handleEvent(event);//этим простым трюком перекладывает отображение ввода на родителя
}//handleEvent
private void calcCode(){
//здесь вычисляем код
//...как именно? - каждый может решить это по-своему
//показать результат
tfCode.setText(new String(code));
}//calcCode
}
|
Если мне не изменяет память, то даже написанная целиком на ассемблере простейшая программа для Windows и то будет "потяжелее".
Примечание: в статье использованы фрагмены условно-бесплатной игры "Таракан"