Язык программирования 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 и то будет "потяжелее".
Примечание: в статье использованы фрагмены условно-бесплатной игры "Таракан"